From 7db95c0f40fb940fe8b194f2c1ff1e5ac1d10c4d Mon Sep 17 00:00:00 2001 From: "nikolai.gurban" Date: Thu, 1 Aug 2024 22:13:06 +0300 Subject: [PATCH] feat: added console --- console/service/Dockerfile | 16 + console/service/Makefile | 31 + console/service/README.md | 104 + console/service/VERSION | 1 + console/service/api/swagger.yaml | 1663 ++++++ console/service/db/README.md | 107 + ...40520144338_2.0.0_initial_scheme_setup.sql | 1035 ++++ console/service/env.sh | 7 + console/service/go.mod | 74 + console/service/go.sum | 238 + .../service/internal/configuration/config.go | 77 + .../controllers/cluster/delete_cluster.go | 27 + .../controllers/cluster/delete_server.go | 41 + .../controllers/cluster/get_cluster.go | 57 + .../cluster/get_cluster_default_name.go | 33 + .../controllers/cluster/get_clusters.go | 107 + .../controllers/cluster/post_cluster.go | 248 + .../cluster/post_cluster_refresh.go | 40 + .../controllers/cluster/remove_cluster.go | 87 + .../internal/controllers/cluster/utils.go | 98 + .../dictionary/get_database_extensions.go | 41 + .../dictionary/get_external_deployments.go | 46 + .../dictionary/get_postgres_versions.go | 31 + .../environment/delete_environment.go | 40 + .../environment/get_environments.go | 36 + .../environment/post_environment.go | 44 + .../service/internal/controllers/errors.go | 5 + .../operation/get_operation_log.go | 34 + .../controllers/operation/get_operations.go | 47 + .../controllers/project/delete_project.go | 61 + .../controllers/project/get_projects.go | 36 + .../controllers/project/path_project.go | 28 + .../controllers/project/post_project.go | 42 + .../controllers/secret/delete_secret.go | 27 + .../controllers/secret/get_secrets.go | 43 + .../controllers/secret/post_secret.go | 76 + .../controllers/setting/get_settings.go | 40 + .../controllers/setting/patch_setting.go | 28 + .../controllers/setting/post_setting.go | 28 + console/service/internal/controllers/utils.go | 11 + console/service/internal/convert/clusters.go | 45 + .../internal/convert/database_extensions.go | 27 + .../service/internal/convert/environments.go | 33 + .../internal/convert/external_deployments.go | 112 + .../service/internal/convert/operations.go | 35 + .../internal/convert/postgres_versions.go | 24 + console/service/internal/convert/projects.go | 33 + console/service/internal/convert/secret.go | 36 + console/service/internal/convert/settings.go | 33 + console/service/internal/db/db.go | 31 + console/service/internal/db/tracer.go | 124 + console/service/internal/service/service.go | 127 + .../service/internal/storage/cluster_flags.go | 16 + .../internal/storage/cluster_flags_test.go | 13 + console/service/internal/storage/consts.go | 55 + .../service/internal/storage/db_storage.go | 816 +++ console/service/internal/storage/istorage.go | 68 + console/service/internal/storage/models.go | 311 + console/service/internal/storage/utils.go | 114 + .../internal/watcher/cluster_watcher.go | 291 + console/service/internal/watcher/consts.go | 10 + .../service/internal/watcher/log_collector.go | 93 + .../service/internal/watcher/log_watcher.go | 209 + console/service/internal/watcher/models.go | 15 + console/service/internal/xdocker/images.go | 7 + console/service/internal/xdocker/imanager.go | 23 + console/service/internal/xdocker/manager.go | 158 + .../service/internal/xdocker/manager_utils.go | 43 + .../internal/xdocker/round_tripper_log.go | 100 + console/service/main.go | 111 + console/service/middleware/authorization.go | 34 + console/service/middleware/cid.go | 29 + console/service/middleware/cors.go | 19 + console/service/middleware/request_log.go | 166 + console/service/middleware/utils.go | 33 + console/service/migrations/goose_logger.go | 22 + console/service/migrations/migrate.go | 18 + console/service/models/cluster_info.go | 170 + .../cluster_info_additional_settings.go | 50 + .../service/models/cluster_info_instance.go | 80 + .../service/models/deployment_cloud_image.go | 90 + .../models/deployment_info_cloud_region.go | 222 + .../models/deployment_instance_type.go | 71 + console/service/models/meta_pagination.go | 56 + .../service/models/request_change_setting.go | 50 + .../service/models/request_cluster_create.go | 161 + .../service/models/request_cluster_reinit.go | 11 + .../service/models/request_cluster_reload.go | 11 + .../service/models/request_cluster_remove.go | 11 + .../service/models/request_cluster_restart.go | 11 + .../service/models/request_cluster_start.go | 11 + .../service/models/request_cluster_stop.go | 11 + .../service/models/request_create_setting.go | 53 + console/service/models/request_environment.go | 55 + .../service/models/request_project_create.go | 55 + .../service/models/request_project_patch.go | 53 + .../service/models/request_secret_create.go | 154 + .../service/models/request_secret_patch.go | 59 + .../service/models/request_secret_value.go | 380 ++ .../models/request_secret_value_aws.go | 53 + .../models/request_secret_value_azure.go | 59 + .../request_secret_value_digital_ocean.go | 50 + .../models/request_secret_value_gcp.go | 50 + .../models/request_secret_value_hetzner.go | 50 + .../models/request_secret_value_password.go | 53 + .../models/request_secret_value_ssh_key.go | 50 + .../service/models/response_cluster_create.go | 53 + .../models/response_cluster_default_name.go | 51 + .../service/models/response_cluster_logs.go | 50 + .../service/models/response_clusters_info.go | 162 + .../models/response_database_extension.go | 75 + .../models/response_database_extensions.go | 162 + .../models/response_deployment_info.go | 507 ++ .../models/response_deployments_info.go | 162 + .../service/models/response_environment.go | 108 + .../models/response_environments_list.go | 162 + console/service/models/response_error.go | 56 + console/service/models/response_operation.go | 116 + .../models/response_operations_list.go | 162 + .../models/response_postgres_version.go | 100 + .../models/response_postgres_versions.go | 116 + console/service/models/response_project.go | 105 + .../service/models/response_projects_list.go | 162 + .../service/models/response_secret_info.go | 163 + .../models/response_secret_info_list.go | 162 + console/service/models/response_setting.go | 103 + console/service/models/response_settings.go | 162 + console/service/models/response_version.go | 51 + console/service/models/secret_type.go | 93 + console/service/pkg/patroni/client.go | 102 + console/service/pkg/patroni/models.go | 20 + console/service/pkg/tracer/cid.go | 3 + .../service/restapi/configure_pg_console.go | 131 + console/service/restapi/doc.go | 20 + console/service/restapi/embedded_spec.go | 4563 +++++++++++++++ .../operations/cluster/delete_clusters_id.go | 56 + .../cluster/delete_clusters_id_parameters.go | 77 + .../cluster/delete_clusters_id_responses.go | 84 + .../cluster/delete_clusters_id_urlbuilder.go | 101 + .../operations/cluster/delete_servers_id.go | 56 + .../cluster/delete_servers_id_parameters.go | 77 + .../cluster/delete_servers_id_responses.go | 107 + .../cluster/delete_servers_id_urlbuilder.go | 101 + .../operations/cluster/get_clusters.go | 56 + .../cluster/get_clusters_default_name.go | 56 + .../get_clusters_default_name_parameters.go | 46 + .../get_clusters_default_name_responses.go | 104 + .../get_clusters_default_name_urlbuilder.go | 87 + .../operations/cluster/get_clusters_id.go | 56 + .../cluster/get_clusters_id_parameters.go | 77 + .../cluster/get_clusters_id_responses.go | 104 + .../cluster/get_clusters_id_urlbuilder.go | 101 + .../cluster/get_clusters_parameters.go | 455 ++ .../cluster/get_clusters_responses.go | 104 + .../cluster/get_clusters_urlbuilder.go | 202 + .../operations/cluster/post_clusters.go | 56 + .../cluster/post_clusters_id_refresh.go | 56 + .../post_clusters_id_refresh_parameters.go | 77 + .../post_clusters_id_refresh_responses.go | 104 + .../post_clusters_id_refresh_urlbuilder.go | 101 + .../cluster/post_clusters_id_reinit.go | 56 + .../post_clusters_id_reinit_parameters.go | 103 + .../post_clusters_id_reinit_responses.go | 104 + .../post_clusters_id_reinit_urlbuilder.go | 101 + .../cluster/post_clusters_id_reload.go | 56 + .../post_clusters_id_reload_parameters.go | 103 + .../post_clusters_id_reload_responses.go | 104 + .../post_clusters_id_reload_urlbuilder.go | 101 + .../cluster/post_clusters_id_remove.go | 56 + .../post_clusters_id_remove_parameters.go | 103 + .../post_clusters_id_remove_responses.go | 84 + .../post_clusters_id_remove_urlbuilder.go | 101 + .../cluster/post_clusters_id_restart.go | 56 + .../post_clusters_id_restart_parameters.go | 103 + .../post_clusters_id_restart_responses.go | 104 + .../post_clusters_id_restart_urlbuilder.go | 101 + .../cluster/post_clusters_id_start.go | 56 + .../post_clusters_id_start_parameters.go | 103 + .../post_clusters_id_start_responses.go | 104 + .../post_clusters_id_start_urlbuilder.go | 101 + .../cluster/post_clusters_id_stop.go | 56 + .../post_clusters_id_stop_parameters.go | 103 + .../post_clusters_id_stop_responses.go | 104 + .../post_clusters_id_stop_urlbuilder.go | 101 + .../cluster/post_clusters_parameters.go | 84 + .../cluster/post_clusters_responses.go | 104 + .../cluster/post_clusters_urlbuilder.go | 87 + .../dictionary/get_database_extensions.go | 56 + .../get_database_extensions_parameters.go | 193 + .../get_database_extensions_responses.go | 104 + .../get_database_extensions_urlbuilder.go | 132 + .../dictionary/get_external_deployments.go | 56 + .../get_external_deployments_parameters.go | 115 + .../get_external_deployments_responses.go | 104 + .../get_external_deployments_urlbuilder.go | 114 + .../dictionary/get_postgres_versions.go | 56 + .../get_postgres_versions_parameters.go | 46 + .../get_postgres_versions_responses.go | 104 + .../get_postgres_versions_urlbuilder.go | 87 + .../environment/delete_environments_id.go | 56 + .../delete_environments_id_parameters.go | 77 + .../delete_environments_id_responses.go | 84 + .../delete_environments_id_urlbuilder.go | 101 + .../environment/get_environments.go | 56 + .../get_environments_parameters.go | 115 + .../environment/get_environments_responses.go | 104 + .../get_environments_urlbuilder.go | 114 + .../environment/post_environments.go | 56 + .../post_environments_parameters.go | 84 + .../post_environments_responses.go | 104 + .../post_environments_urlbuilder.go | 87 + .../operations/operation/get_operations.go | 56 + .../operation/get_operations_id_log.go | 56 + .../get_operations_id_log_parameters.go | 77 + .../get_operations_id_log_responses.go | 147 + .../get_operations_id_log_urlbuilder.go | 101 + .../operation/get_operations_parameters.go | 397 ++ .../operation/get_operations_responses.go | 104 + .../operation/get_operations_urlbuilder.go | 178 + .../restapi/operations/pg_console_api.go | 706 +++ .../operations/project/delete_projects_id.go | 56 + .../project/delete_projects_id_parameters.go | 77 + .../project/delete_projects_id_responses.go | 84 + .../project/delete_projects_id_urlbuilder.go | 101 + .../operations/project/get_projects.go | 56 + .../project/get_projects_parameters.go | 115 + .../project/get_projects_responses.go | 104 + .../project/get_projects_urlbuilder.go | 114 + .../operations/project/patch_projects_id.go | 56 + .../project/patch_projects_id_parameters.go | 115 + .../project/patch_projects_id_responses.go | 104 + .../project/patch_projects_id_urlbuilder.go | 101 + .../operations/project/post_projects.go | 56 + .../project/post_projects_parameters.go | 84 + .../project/post_projects_responses.go | 104 + .../project/post_projects_urlbuilder.go | 87 + .../operations/secret/delete_secrets_id.go | 56 + .../secret/delete_secrets_id_parameters.go | 77 + .../secret/delete_secrets_id_responses.go | 84 + .../secret/delete_secrets_id_urlbuilder.go | 101 + .../restapi/operations/secret/get_secrets.go | 56 + .../secret/get_secrets_parameters.go | 233 + .../secret/get_secrets_responses.go | 104 + .../secret/get_secrets_urlbuilder.go | 147 + .../operations/secret/patch_secrets_id.go | 56 + .../secret/patch_secrets_id_parameters.go | 115 + .../secret/patch_secrets_id_responses.go | 104 + .../secret/patch_secrets_id_urlbuilder.go | 101 + .../restapi/operations/secret/post_secrets.go | 56 + .../secret/post_secrets_parameters.go | 84 + .../secret/post_secrets_responses.go | 104 + .../secret/post_secrets_urlbuilder.go | 87 + .../operations/setting/get_settings.go | 56 + .../setting/get_settings_parameters.go | 142 + .../setting/get_settings_responses.go | 104 + .../setting/get_settings_urlbuilder.go | 123 + .../operations/setting/patch_settings_name.go | 56 + .../setting/patch_settings_name_parameters.go | 109 + .../setting/patch_settings_name_responses.go | 104 + .../setting/patch_settings_name_urlbuilder.go | 99 + .../operations/setting/post_settings.go | 56 + .../setting/post_settings_parameters.go | 84 + .../setting/post_settings_responses.go | 104 + .../setting/post_settings_urlbuilder.go | 87 + .../restapi/operations/system/get_version.go | 56 + .../system/get_version_parameters.go | 46 + .../system/get_version_responses.go | 59 + .../system/get_version_urlbuilder.go | 87 + console/service/restapi/server.go | 508 ++ console/ui/.env | 6 + console/ui/.env.production | 6 + console/ui/.eslintrc.cjs | 28 + console/ui/.gitignore | 24 + console/ui/.prettierignore | 6 + console/ui/.prettierrc | 12 + console/ui/Dockerfile | 24 + console/ui/README.md | 140 + console/ui/env.sh | 21 + console/ui/index.html | 13 + console/ui/nginx/nginx.conf | 48 + console/ui/package.json | 77 + console/ui/src/app/App.tsx | 24 + console/ui/src/app/layout/index.ts | 3 + console/ui/src/app/layout/ui/index.tsx | 16 + console/ui/src/app/main.tsx | 16 + .../slices/projectSlice/projectSelectors.ts | 3 + .../redux/slices/projectSlice/projectSlice.ts | 24 + console/ui/src/app/redux/store/hooks.ts | 6 + console/ui/src/app/redux/store/store.ts | 71 + .../src/app/router/PrivateRouterWrapper.tsx | 23 + console/ui/src/app/router/Router.tsx | 49 + .../router/routerConfig/ClustersRoutes.tsx | 37 + .../router/routerConfig/OperationsRoutes.tsx | 29 + .../router/routerConfig/SettingsRoutes.tsx | 28 + .../src/app/router/routerPathsConfig/index.ts | 20 + .../routerClustersPathsConfig.ts | 13 + .../routerOperationsPathsConfig.ts | 9 + .../routerSettingsPathsConfig.ts | 21 + console/ui/src/app/vite-env.d.ts | 2 + .../index.ts | 3 + .../model/constants.ts | 16 + .../ui/AuthenticationFormPart.tsx | 45 + .../ui/PasswordMethodFormPart.tsx | 37 + .../ui/SshMethodFormPart.tsx | 40 + .../ui/index.tsx | 187 + .../ui/src/entities/breadcumb-item/index.ts | 3 + .../entities/breadcumb-item/model/types.ts | 4 + .../src/entities/breadcumb-item/ui/index.tsx | 11 + .../cluster-cloud-provider-block/index.ts | 3 + .../model/types.ts | 6 + .../cluster-cloud-provider-block/ui/index.tsx | 23 + .../cluster-description-block/index.ts | 3 + .../cluster-description-block/ui/index.tsx | 35 + .../assets/aws.svg | 1 + .../assets/azure.svg | 30 + .../assets/digitalocean.svg | 20 + .../assets/gcp.svg | 17 + .../assets/hetzner.svg | 9 + .../cluster-form-cloud-region-block/index.ts | 3 + .../lib/hooks.tsx | 15 + .../model/types.ts | 5 + .../ui/index.tsx | 81 + .../cluster-form-cluster-name-block/index.ts | 3 + .../ui/index.tsx | 37 + .../cluster-form-environment-block/index.ts | 3 + .../model/types.ts | 5 + .../ui/index.tsx | 34 + .../assets/instancesIcon.svg | 9 + .../index.ts | 3 + .../ui/index.tsx | 42 + .../cluster-form-instances-block/index.ts | 3 + .../model/types.ts | 9 + .../cluster-form-instances-block/ui/index.tsx | 79 + console/ui/src/entities/cluster-info/index.ts | 3 + .../src/entities/cluster-info/lib/hooks.tsx | 40 + .../src/entities/cluster-info/model/types.ts | 7 + .../ui/src/entities/cluster-info/ui/index.tsx | 34 + .../cluster-instance-config-box/index.ts | 3 + .../model/types.ts | 7 + .../cluster-instance-config-box/ui/index.tsx | 32 + .../cluster-name-description-block/index.ts | 3 + .../ui/index.tsx | 35 + .../connection-info/assets/eyeIcon.svg | 4 + .../ui/src/entities/connection-info/index.ts | 3 + .../entities/connection-info/lib/hooks.tsx | 65 + .../entities/connection-info/model/types.ts | 14 + .../ui/ConnectionInfoRowConteiner.tsx | 13 + .../src/entities/connection-info/ui/index.tsx | 28 + .../entities/database-servers-block/index.ts | 3 + .../database-servers-block/model/types.ts | 6 + .../ui/DatabaseServerBox.tsx | 81 + .../database-servers-block/ui/index.tsx | 40 + .../entities/load-balancers-block/index.ts | 3 + .../load-balancers-block/ui/index.tsx | 32 + .../entities/postgres-version-block/index.ts | 3 + .../postgres-version-block/model/types.ts | 5 + .../postgres-version-block/ui/index.tsx | 43 + .../ui/src/entities/providers-block/index.ts | 3 + .../entities/providers-block/model/types.ts | 10 + .../ui/ClusterFormCloudProviderBox.tsx | 23 + .../src/entities/providers-block/ui/index.tsx | 81 + .../src/entities/secret-form-block/index.ts | 3 + .../secret-form-block/lib/functions.ts | 118 + .../secret-form-block/model/constants.ts | 30 + .../entities/secret-form-block/model/types.ts | 28 + .../secret-form-block/ui/AwsSecret.tsx | 49 + .../secret-form-block/ui/AzureSecret.tsx | 79 + .../ui/DigitalOceanSecret.tsx | 34 + .../secret-form-block/ui/GcpSecret.tsx | 36 + .../secret-form-block/ui/HetznerSecret.tsx | 34 + .../secret-form-block/ui/PasswordSecret.tsx | 52 + .../secret-form-block/ui/SshKeySecret.tsx | 38 + .../entities/secret-form-block/ui/index.tsx | 30 + .../entities/settings-proxy-block/index.ts | 3 + .../settings-proxy-block/model/constants.ts | 4 + .../settings-proxy-block/model/types.ts | 6 + .../settings-proxy-block/ui/index.tsx | 44 + console/ui/src/entities/sidebar-item/index.ts | 3 + .../src/entities/sidebar-item/model/types.ts | 8 + .../sidebar-item/ui/SidebarItemContent.tsx | 36 + .../ui/src/entities/sidebar-item/ui/index.tsx | 36 + .../ui/src/entities/ssh-key-block/index.ts | 3 + .../src/entities/ssh-key-block/ui/index.tsx | 40 + .../ui/src/entities/storage-block/index.ts | 3 + .../entities/storage-block/lib/functions.ts | 0 .../src/entities/storage-block/ui/index.tsx | 50 + .../src/entities/vip-address-block/index.ts | 3 + .../entities/vip-address-block/ui/index.tsx | 38 + .../ui/src/features/add-environment/index.ts | 3 + .../src/features/add-environment/ui/index.tsx | 41 + console/ui/src/features/add-project/index.ts | 3 + .../features/add-project/model/constants.ts | 4 + .../src/features/add-project/model/types.ts | 6 + .../features/add-project/model/validation.ts | 9 + .../ui/src/features/add-project/ui/index.tsx | 54 + console/ui/src/features/add-secret/index.ts | 3 + .../features/add-secret/model/constants.ts | 6 + .../ui/src/features/add-secret/model/types.ts | 7 + .../features/add-secret/model/validation.ts | 49 + .../ui/src/features/add-secret/ui/index.tsx | 150 + .../bradcrumbs/hooks/useBreadcrumbs.tsx | 19 + console/ui/src/features/bradcrumbs/index.ts | 3 + .../ui/src/features/bradcrumbs/ui/index.tsx | 35 + .../features/cluster-secret-modal/index.ts | 3 + .../cluster-secret-modal/lib/functions.ts | 169 + .../cluster-secret-modal/model/constants.ts | 16 + .../cluster-secret-modal/model/types.ts | 53 + .../cluster-secret-modal/model/validation.ts | 0 .../cluster-secret-modal/ui/index.tsx | 219 + .../index.ts | 3 + .../ui/index.tsx | 47 + .../features/clusters-table-buttons/index.ts | 3 + .../clusters-table-buttons/model/types.ts | 3 + .../clusters-table-buttons/ui/index.tsx | 35 + .../clusters-table-row-actions/index.ts | 3 + .../clusters-table-row-actions/model/types.ts | 5 + .../ui/ClusterTableRemoveButton.tsx | 71 + .../clusters-table-row-actions/ui/index.tsx | 14 + .../ui/index.tsx | 42 + .../ui/src/features/logout-button/index.ts | 3 + .../src/features/logout-button/ui/index.tsx | 23 + .../operations-table-buttons/index.ts | 3 + .../operations-table-buttons/lib/functions.ts | 61 + .../model/constants.ts | 8 + .../operations-table-buttons/model/types.ts | 5 + .../operations-table-buttons/ui/index.tsx | 53 + .../operations-table-row-actions/index.ts | 3 + .../operations-table-row-actions/ui/index.tsx | 25 + .../pojects-table-row-actions/index.ts | 3 + .../pojects-table-row-actions/ui/index.tsx | 48 + .../features/settings-table-buttons/index.ts | 3 + .../settings-table-buttons/lib/functions.ts | 3 + .../settings-table-buttons/ui/index.tsx | 16 + .../settings-table-row-actions/index.ts | 3 + .../model/constants.ts | 1 + .../settings-table-row-actions/ui/index.tsx | 57 + console/ui/src/pages/404/index.ts | 3 + console/ui/src/pages/404/ui/illustration.tsx | 14 + console/ui/src/pages/404/ui/index.tsx | 39 + console/ui/src/pages/add-cluster/index.ts | 3 + console/ui/src/pages/add-cluster/ui/index.tsx | 8 + console/ui/src/pages/clusters/index.ts | 3 + console/ui/src/pages/clusters/ui/index.tsx | 13 + console/ui/src/pages/login/index.ts | 3 + console/ui/src/pages/login/model/constants.ts | 3 + console/ui/src/pages/login/model/types.ts | 5 + console/ui/src/pages/login/ui/index.tsx | 75 + console/ui/src/pages/operation-log/index.ts | 3 + .../ui/src/pages/operation-log/ui/index.tsx | 39 + console/ui/src/pages/operations/index.ts | 3 + console/ui/src/pages/operations/ui/index.tsx | 13 + .../ui/src/pages/overview-cluster/index.ts | 3 + .../src/pages/overview-cluster/ui/index.tsx | 49 + console/ui/src/pages/settings/index.ts | 3 + .../ui/src/pages/settings/model/constants.ts | 20 + console/ui/src/pages/settings/ui/index.tsx | 31 + console/ui/src/shared/api/api/clusters.ts | 268 + console/ui/src/shared/api/api/deployments.ts | 94 + console/ui/src/shared/api/api/environments.ts | 70 + console/ui/src/shared/api/api/operations.ts | 89 + console/ui/src/shared/api/api/other.ts | 86 + console/ui/src/shared/api/api/projects.ts | 81 + console/ui/src/shared/api/api/secrets.ts | 164 + console/ui/src/shared/api/api/settings.ts | 76 + console/ui/src/shared/api/apiConfig.ts | 45 + console/ui/src/shared/api/baseApi.ts | 16 + .../ui/src/shared/api/enhancedSecretsApi.ts | 15 + console/ui/src/shared/assets/PGCLogo.svg | 9 + .../src/shared/assets/calendarClockICon.svg | 1 + console/ui/src/shared/assets/checkIcon.svg | 1 + console/ui/src/shared/assets/clustersIcon.svg | 64 + console/ui/src/shared/assets/collapseIcon.svg | 1 + console/ui/src/shared/assets/cpuIcon.svg | 2 + console/ui/src/shared/assets/databaseIcon.svg | 30 + console/ui/src/shared/assets/docsIcon.svg | 1 + console/ui/src/shared/assets/flagIcon.svg | 4 + console/ui/src/shared/assets/githubIcon.svg | 1 + console/ui/src/shared/assets/instanceIcon.svg | 11 + console/ui/src/shared/assets/lanIcon.svg | 1 + console/ui/src/shared/assets/logoutIcon.svg | 1 + console/ui/src/shared/assets/memoryIcon.svg | 2 + .../ui/src/shared/assets/operationsIcon.svg | 1 + console/ui/src/shared/assets/ramIcon.svg | 55 + console/ui/src/shared/assets/serversIcon.svg | 112 + console/ui/src/shared/assets/settingsIcon.svg | 58 + console/ui/src/shared/assets/sponsorIcon.svg | 1 + console/ui/src/shared/assets/storageIcon.svg | 11 + console/ui/src/shared/assets/supportIcon.svg | 1 + console/ui/src/shared/config/constants.ts | 32 + console/ui/src/shared/i18n/i18n.ts | 36 + .../src/shared/i18n/locales/en/clusters.json | 70 + .../shared/i18n/locales/en/operations.json | 16 + .../src/shared/i18n/locales/en/settings.json | 27 + .../ui/src/shared/i18n/locales/en/shared.json | 45 + .../ui/src/shared/i18n/locales/en/toasts.json | 18 + .../shared/i18n/locales/en/validation.json | 5 + console/ui/src/shared/lib/functions.ts | 43 + console/ui/src/shared/lib/hooks.tsx | 60 + console/ui/src/shared/model/constants.ts | 5 + console/ui/src/shared/model/types.ts | 6 + console/ui/src/shared/theme/theme.ts | 36 + .../shared/ui/copy-icon/assets/copyIcon.svg | 3 + console/ui/src/shared/ui/copy-icon/index.ts | 3 + .../ui/src/shared/ui/copy-icon/model/types.ts | 3 + .../ui/src/shared/ui/copy-icon/ui/index.tsx | 17 + .../shared/ui/default-form-buttons/index.ts | 3 + .../ui/default-form-buttons/model/types.ts | 10 + .../ui/default-form-buttons/ui/index.tsx | 51 + .../ui/src/shared/ui/default-table/index.ts | 3 + .../src/shared/ui/default-table/ui/index.tsx | 44 + .../ui/src/shared/ui/info-card-body/index.ts | 3 + .../shared/ui/info-card-body/model/types.ts | 8 + .../src/shared/ui/info-card-body/ui/index.tsx | 27 + .../ui/src/shared/ui/selectable-box/index.ts | 3 + .../shared/ui/selectable-box/model/types.ts | 8 + .../src/shared/ui/selectable-box/ui/index.tsx | 21 + .../ui/settings-add-entity/model/constants.ts | 4 + .../ui/settings-add-entity/model/types.ts | 13 + .../settings-add-entity/model/validation.ts | 9 + .../ui/settings-add-entity/ui/index.tsx | 120 + console/ui/src/shared/ui/slider-box/index.ts | 3 + .../src/shared/ui/slider-box/lib/functions.ts | 17 + .../src/shared/ui/slider-box/model/types.ts | 29 + .../ui/src/shared/ui/slider-box/ui/index.tsx | 67 + console/ui/src/shared/ui/spinner/index.ts | 3 + console/ui/src/shared/ui/spinner/ui/index.tsx | 12 + console/ui/src/widgets/cluster-form/index.ts | 3 + .../widgets/cluster-form/model/constants.ts | 36 + .../src/widgets/cluster-form/model/types.ts | 13 + .../widgets/cluster-form/model/validation.ts | 203 + .../ui/ClusterFormCloudProviderFormPart.tsx | 18 + .../ui/ClusterFormLocalMachineFormPart.tsx | 16 + .../ui/ClusterFormRegionConfigBox.tsx | 22 + .../ui/src/widgets/cluster-form/ui/index.tsx | 210 + .../widgets/cluster-overview-table/index.ts | 3 + .../cluster-overview-table/lib/hooks.tsx | 28 + .../cluster-overview-table/model/constants.ts | 49 + .../cluster-overview-table/model/types.ts | 19 + .../ui/ClustersOverviewTableButtons.tsx | 27 + .../cluster-overview-table/ui/index.tsx | 56 + .../cluster-summary/assets/awsIcon.svg | 1 + .../cluster-summary/assets/azureIcon.svg | 27 + .../assets/digitaloceanIcon.svg | 1 + .../cluster-summary/assets/gcpIcon.svg | 23 + .../cluster-summary/assets/hetznerIcon.svg | 1 + .../cluster-summary/assets/hetznerIcon2.svg | 1 + .../ui/src/widgets/cluster-summary/index.ts | 3 + .../src/widgets/cluster-summary/lib/hooks.tsx | 205 + .../cluster-summary/model/constants.ts | 22 + .../widgets/cluster-summary/model/types.ts | 37 + .../src/widgets/cluster-summary/ui/index.tsx | 50 + .../clusters-table/assets/correctIcon.svg | 1 + .../clusters-table/assets/errorIcon.svg | 1 + .../clusters-table/assets/noClustersIcon.svg | 9 + .../clusters-table/assets/warningIcon.svg | 1 + .../ui/src/widgets/clusters-table/index.ts | 3 + .../widgets/clusters-table/lib/functions.ts | 82 + .../src/widgets/clusters-table/lib/hooks.tsx | 48 + .../widgets/clusters-table/model/constants.ts | 33 + .../src/widgets/clusters-table/model/types.ts | 11 + .../ui/ClustersEmptyRowsFallback.tsx | 32 + .../src/widgets/clusters-table/ui/index.tsx | 99 + .../src/widgets/environments-table/index.ts | 3 + .../widgets/environments-table/lib/hooks.tsx | 16 + .../environments-table/model/constants.ts | 44 + .../widgets/environments-table/model/types.ts | 9 + .../ui/EnvironmentsTableButtons.tsx | 13 + .../widgets/environments-table/ui/index.tsx | 58 + console/ui/src/widgets/header/index.ts | 3 + console/ui/src/widgets/header/ui/index.tsx | 64 + console/ui/src/widgets/main/index.ts | 3 + console/ui/src/widgets/main/ui/index.tsx | 22 + .../ui/src/widgets/operations-table/index.ts | 3 + .../widgets/operations-table/lib/hooks.tsx | 18 + .../operations-table/model/constants.ts | 59 + .../widgets/operations-table/model/types.ts | 11 + .../src/widgets/operations-table/ui/index.tsx | 90 + .../ui/src/widgets/projects-table/index.tsx | 3 + .../src/widgets/projects-table/lib/hooks.tsx | 16 + .../widgets/projects-table/model/constants.ts | 44 + .../src/widgets/projects-table/model/types.ts | 6 + .../ui/ProjectsTableButtons.tsx | 13 + .../src/widgets/projects-table/ui/index.tsx | 58 + console/ui/src/widgets/secrets-table/index.ts | 3 + .../src/widgets/secrets-table/lib/hooks.tsx | 18 + .../widgets/secrets-table/model/constants.ts | 51 + .../src/widgets/secrets-table/model/types.ts | 10 + .../ui/src/widgets/secrets-table/ui/index.tsx | 64 + console/ui/src/widgets/settings-form/index.ts | 3 + .../ui/src/widgets/settings-form/ui/index.tsx | 98 + console/ui/src/widgets/sidebar/index.ts | 3 + .../ui/src/widgets/sidebar/model/constants.ts | 54 + console/ui/src/widgets/sidebar/ui/index.tsx | 86 + console/ui/tsconfig.json | 61 + console/ui/tsconfig.node.json | 11 + console/ui/vite.config.mts | 28 + console/ui/yarn.lock | 5015 +++++++++++++++++ 597 files changed, 46661 insertions(+) create mode 100644 console/service/Dockerfile create mode 100644 console/service/Makefile create mode 100644 console/service/README.md create mode 100644 console/service/VERSION create mode 100644 console/service/api/swagger.yaml create mode 100644 console/service/db/README.md create mode 100644 console/service/db/migrations/20240520144338_2.0.0_initial_scheme_setup.sql create mode 100755 console/service/env.sh create mode 100644 console/service/go.mod create mode 100644 console/service/go.sum create mode 100644 console/service/internal/configuration/config.go create mode 100644 console/service/internal/controllers/cluster/delete_cluster.go create mode 100644 console/service/internal/controllers/cluster/delete_server.go create mode 100644 console/service/internal/controllers/cluster/get_cluster.go create mode 100644 console/service/internal/controllers/cluster/get_cluster_default_name.go create mode 100644 console/service/internal/controllers/cluster/get_clusters.go create mode 100644 console/service/internal/controllers/cluster/post_cluster.go create mode 100644 console/service/internal/controllers/cluster/post_cluster_refresh.go create mode 100644 console/service/internal/controllers/cluster/remove_cluster.go create mode 100644 console/service/internal/controllers/cluster/utils.go create mode 100644 console/service/internal/controllers/dictionary/get_database_extensions.go create mode 100644 console/service/internal/controllers/dictionary/get_external_deployments.go create mode 100644 console/service/internal/controllers/dictionary/get_postgres_versions.go create mode 100644 console/service/internal/controllers/environment/delete_environment.go create mode 100644 console/service/internal/controllers/environment/get_environments.go create mode 100644 console/service/internal/controllers/environment/post_environment.go create mode 100644 console/service/internal/controllers/errors.go create mode 100644 console/service/internal/controllers/operation/get_operation_log.go create mode 100644 console/service/internal/controllers/operation/get_operations.go create mode 100644 console/service/internal/controllers/project/delete_project.go create mode 100644 console/service/internal/controllers/project/get_projects.go create mode 100644 console/service/internal/controllers/project/path_project.go create mode 100644 console/service/internal/controllers/project/post_project.go create mode 100644 console/service/internal/controllers/secret/delete_secret.go create mode 100644 console/service/internal/controllers/secret/get_secrets.go create mode 100644 console/service/internal/controllers/secret/post_secret.go create mode 100644 console/service/internal/controllers/setting/get_settings.go create mode 100644 console/service/internal/controllers/setting/patch_setting.go create mode 100644 console/service/internal/controllers/setting/post_setting.go create mode 100644 console/service/internal/controllers/utils.go create mode 100644 console/service/internal/convert/clusters.go create mode 100644 console/service/internal/convert/database_extensions.go create mode 100644 console/service/internal/convert/environments.go create mode 100644 console/service/internal/convert/external_deployments.go create mode 100644 console/service/internal/convert/operations.go create mode 100644 console/service/internal/convert/postgres_versions.go create mode 100644 console/service/internal/convert/projects.go create mode 100644 console/service/internal/convert/secret.go create mode 100644 console/service/internal/convert/settings.go create mode 100644 console/service/internal/db/db.go create mode 100644 console/service/internal/db/tracer.go create mode 100644 console/service/internal/service/service.go create mode 100644 console/service/internal/storage/cluster_flags.go create mode 100644 console/service/internal/storage/cluster_flags_test.go create mode 100644 console/service/internal/storage/consts.go create mode 100644 console/service/internal/storage/db_storage.go create mode 100644 console/service/internal/storage/istorage.go create mode 100644 console/service/internal/storage/models.go create mode 100644 console/service/internal/storage/utils.go create mode 100644 console/service/internal/watcher/cluster_watcher.go create mode 100644 console/service/internal/watcher/consts.go create mode 100644 console/service/internal/watcher/log_collector.go create mode 100644 console/service/internal/watcher/log_watcher.go create mode 100644 console/service/internal/watcher/models.go create mode 100644 console/service/internal/xdocker/images.go create mode 100644 console/service/internal/xdocker/imanager.go create mode 100644 console/service/internal/xdocker/manager.go create mode 100644 console/service/internal/xdocker/manager_utils.go create mode 100644 console/service/internal/xdocker/round_tripper_log.go create mode 100644 console/service/main.go create mode 100644 console/service/middleware/authorization.go create mode 100644 console/service/middleware/cid.go create mode 100644 console/service/middleware/cors.go create mode 100644 console/service/middleware/request_log.go create mode 100644 console/service/middleware/utils.go create mode 100644 console/service/migrations/goose_logger.go create mode 100644 console/service/migrations/migrate.go create mode 100644 console/service/models/cluster_info.go create mode 100644 console/service/models/cluster_info_additional_settings.go create mode 100644 console/service/models/cluster_info_instance.go create mode 100644 console/service/models/deployment_cloud_image.go create mode 100644 console/service/models/deployment_info_cloud_region.go create mode 100644 console/service/models/deployment_instance_type.go create mode 100644 console/service/models/meta_pagination.go create mode 100644 console/service/models/request_change_setting.go create mode 100644 console/service/models/request_cluster_create.go create mode 100644 console/service/models/request_cluster_reinit.go create mode 100644 console/service/models/request_cluster_reload.go create mode 100644 console/service/models/request_cluster_remove.go create mode 100644 console/service/models/request_cluster_restart.go create mode 100644 console/service/models/request_cluster_start.go create mode 100644 console/service/models/request_cluster_stop.go create mode 100644 console/service/models/request_create_setting.go create mode 100644 console/service/models/request_environment.go create mode 100644 console/service/models/request_project_create.go create mode 100644 console/service/models/request_project_patch.go create mode 100644 console/service/models/request_secret_create.go create mode 100644 console/service/models/request_secret_patch.go create mode 100644 console/service/models/request_secret_value.go create mode 100644 console/service/models/request_secret_value_aws.go create mode 100644 console/service/models/request_secret_value_azure.go create mode 100644 console/service/models/request_secret_value_digital_ocean.go create mode 100644 console/service/models/request_secret_value_gcp.go create mode 100644 console/service/models/request_secret_value_hetzner.go create mode 100644 console/service/models/request_secret_value_password.go create mode 100644 console/service/models/request_secret_value_ssh_key.go create mode 100644 console/service/models/response_cluster_create.go create mode 100644 console/service/models/response_cluster_default_name.go create mode 100644 console/service/models/response_cluster_logs.go create mode 100644 console/service/models/response_clusters_info.go create mode 100644 console/service/models/response_database_extension.go create mode 100644 console/service/models/response_database_extensions.go create mode 100644 console/service/models/response_deployment_info.go create mode 100644 console/service/models/response_deployments_info.go create mode 100644 console/service/models/response_environment.go create mode 100644 console/service/models/response_environments_list.go create mode 100644 console/service/models/response_error.go create mode 100644 console/service/models/response_operation.go create mode 100644 console/service/models/response_operations_list.go create mode 100644 console/service/models/response_postgres_version.go create mode 100644 console/service/models/response_postgres_versions.go create mode 100644 console/service/models/response_project.go create mode 100644 console/service/models/response_projects_list.go create mode 100644 console/service/models/response_secret_info.go create mode 100644 console/service/models/response_secret_info_list.go create mode 100644 console/service/models/response_setting.go create mode 100644 console/service/models/response_settings.go create mode 100644 console/service/models/response_version.go create mode 100644 console/service/models/secret_type.go create mode 100644 console/service/pkg/patroni/client.go create mode 100644 console/service/pkg/patroni/models.go create mode 100644 console/service/pkg/tracer/cid.go create mode 100644 console/service/restapi/configure_pg_console.go create mode 100644 console/service/restapi/doc.go create mode 100644 console/service/restapi/embedded_spec.go create mode 100644 console/service/restapi/operations/cluster/delete_clusters_id.go create mode 100644 console/service/restapi/operations/cluster/delete_clusters_id_parameters.go create mode 100644 console/service/restapi/operations/cluster/delete_clusters_id_responses.go create mode 100644 console/service/restapi/operations/cluster/delete_clusters_id_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/delete_servers_id.go create mode 100644 console/service/restapi/operations/cluster/delete_servers_id_parameters.go create mode 100644 console/service/restapi/operations/cluster/delete_servers_id_responses.go create mode 100644 console/service/restapi/operations/cluster/delete_servers_id_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/get_clusters.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_default_name.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_default_name_parameters.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_default_name_responses.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_default_name_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_id.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_id_parameters.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_id_responses.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_id_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_parameters.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_responses.go create mode 100644 console/service/restapi/operations/cluster/get_clusters_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_refresh.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_refresh_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_refresh_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_refresh_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reinit.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reinit_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reinit_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reinit_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reload.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reload_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reload_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_reload_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_remove.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_remove_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_remove_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_remove_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_restart.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_restart_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_restart_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_restart_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_start.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_start_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_start_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_start_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_stop.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_stop_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_stop_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_id_stop_urlbuilder.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_parameters.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_responses.go create mode 100644 console/service/restapi/operations/cluster/post_clusters_urlbuilder.go create mode 100644 console/service/restapi/operations/dictionary/get_database_extensions.go create mode 100644 console/service/restapi/operations/dictionary/get_database_extensions_parameters.go create mode 100644 console/service/restapi/operations/dictionary/get_database_extensions_responses.go create mode 100644 console/service/restapi/operations/dictionary/get_database_extensions_urlbuilder.go create mode 100644 console/service/restapi/operations/dictionary/get_external_deployments.go create mode 100644 console/service/restapi/operations/dictionary/get_external_deployments_parameters.go create mode 100644 console/service/restapi/operations/dictionary/get_external_deployments_responses.go create mode 100644 console/service/restapi/operations/dictionary/get_external_deployments_urlbuilder.go create mode 100644 console/service/restapi/operations/dictionary/get_postgres_versions.go create mode 100644 console/service/restapi/operations/dictionary/get_postgres_versions_parameters.go create mode 100644 console/service/restapi/operations/dictionary/get_postgres_versions_responses.go create mode 100644 console/service/restapi/operations/dictionary/get_postgres_versions_urlbuilder.go create mode 100644 console/service/restapi/operations/environment/delete_environments_id.go create mode 100644 console/service/restapi/operations/environment/delete_environments_id_parameters.go create mode 100644 console/service/restapi/operations/environment/delete_environments_id_responses.go create mode 100644 console/service/restapi/operations/environment/delete_environments_id_urlbuilder.go create mode 100644 console/service/restapi/operations/environment/get_environments.go create mode 100644 console/service/restapi/operations/environment/get_environments_parameters.go create mode 100644 console/service/restapi/operations/environment/get_environments_responses.go create mode 100644 console/service/restapi/operations/environment/get_environments_urlbuilder.go create mode 100644 console/service/restapi/operations/environment/post_environments.go create mode 100644 console/service/restapi/operations/environment/post_environments_parameters.go create mode 100644 console/service/restapi/operations/environment/post_environments_responses.go create mode 100644 console/service/restapi/operations/environment/post_environments_urlbuilder.go create mode 100644 console/service/restapi/operations/operation/get_operations.go create mode 100644 console/service/restapi/operations/operation/get_operations_id_log.go create mode 100644 console/service/restapi/operations/operation/get_operations_id_log_parameters.go create mode 100644 console/service/restapi/operations/operation/get_operations_id_log_responses.go create mode 100644 console/service/restapi/operations/operation/get_operations_id_log_urlbuilder.go create mode 100644 console/service/restapi/operations/operation/get_operations_parameters.go create mode 100644 console/service/restapi/operations/operation/get_operations_responses.go create mode 100644 console/service/restapi/operations/operation/get_operations_urlbuilder.go create mode 100644 console/service/restapi/operations/pg_console_api.go create mode 100644 console/service/restapi/operations/project/delete_projects_id.go create mode 100644 console/service/restapi/operations/project/delete_projects_id_parameters.go create mode 100644 console/service/restapi/operations/project/delete_projects_id_responses.go create mode 100644 console/service/restapi/operations/project/delete_projects_id_urlbuilder.go create mode 100644 console/service/restapi/operations/project/get_projects.go create mode 100644 console/service/restapi/operations/project/get_projects_parameters.go create mode 100644 console/service/restapi/operations/project/get_projects_responses.go create mode 100644 console/service/restapi/operations/project/get_projects_urlbuilder.go create mode 100644 console/service/restapi/operations/project/patch_projects_id.go create mode 100644 console/service/restapi/operations/project/patch_projects_id_parameters.go create mode 100644 console/service/restapi/operations/project/patch_projects_id_responses.go create mode 100644 console/service/restapi/operations/project/patch_projects_id_urlbuilder.go create mode 100644 console/service/restapi/operations/project/post_projects.go create mode 100644 console/service/restapi/operations/project/post_projects_parameters.go create mode 100644 console/service/restapi/operations/project/post_projects_responses.go create mode 100644 console/service/restapi/operations/project/post_projects_urlbuilder.go create mode 100644 console/service/restapi/operations/secret/delete_secrets_id.go create mode 100644 console/service/restapi/operations/secret/delete_secrets_id_parameters.go create mode 100644 console/service/restapi/operations/secret/delete_secrets_id_responses.go create mode 100644 console/service/restapi/operations/secret/delete_secrets_id_urlbuilder.go create mode 100644 console/service/restapi/operations/secret/get_secrets.go create mode 100644 console/service/restapi/operations/secret/get_secrets_parameters.go create mode 100644 console/service/restapi/operations/secret/get_secrets_responses.go create mode 100644 console/service/restapi/operations/secret/get_secrets_urlbuilder.go create mode 100644 console/service/restapi/operations/secret/patch_secrets_id.go create mode 100644 console/service/restapi/operations/secret/patch_secrets_id_parameters.go create mode 100644 console/service/restapi/operations/secret/patch_secrets_id_responses.go create mode 100644 console/service/restapi/operations/secret/patch_secrets_id_urlbuilder.go create mode 100644 console/service/restapi/operations/secret/post_secrets.go create mode 100644 console/service/restapi/operations/secret/post_secrets_parameters.go create mode 100644 console/service/restapi/operations/secret/post_secrets_responses.go create mode 100644 console/service/restapi/operations/secret/post_secrets_urlbuilder.go create mode 100644 console/service/restapi/operations/setting/get_settings.go create mode 100644 console/service/restapi/operations/setting/get_settings_parameters.go create mode 100644 console/service/restapi/operations/setting/get_settings_responses.go create mode 100644 console/service/restapi/operations/setting/get_settings_urlbuilder.go create mode 100644 console/service/restapi/operations/setting/patch_settings_name.go create mode 100644 console/service/restapi/operations/setting/patch_settings_name_parameters.go create mode 100644 console/service/restapi/operations/setting/patch_settings_name_responses.go create mode 100644 console/service/restapi/operations/setting/patch_settings_name_urlbuilder.go create mode 100644 console/service/restapi/operations/setting/post_settings.go create mode 100644 console/service/restapi/operations/setting/post_settings_parameters.go create mode 100644 console/service/restapi/operations/setting/post_settings_responses.go create mode 100644 console/service/restapi/operations/setting/post_settings_urlbuilder.go create mode 100644 console/service/restapi/operations/system/get_version.go create mode 100644 console/service/restapi/operations/system/get_version_parameters.go create mode 100644 console/service/restapi/operations/system/get_version_responses.go create mode 100644 console/service/restapi/operations/system/get_version_urlbuilder.go create mode 100644 console/service/restapi/server.go create mode 100644 console/ui/.env create mode 100644 console/ui/.env.production create mode 100644 console/ui/.eslintrc.cjs create mode 100644 console/ui/.gitignore create mode 100644 console/ui/.prettierignore create mode 100644 console/ui/.prettierrc create mode 100644 console/ui/Dockerfile create mode 100644 console/ui/README.md create mode 100644 console/ui/env.sh create mode 100644 console/ui/index.html create mode 100644 console/ui/nginx/nginx.conf create mode 100644 console/ui/package.json create mode 100644 console/ui/src/app/App.tsx create mode 100644 console/ui/src/app/layout/index.ts create mode 100644 console/ui/src/app/layout/ui/index.tsx create mode 100644 console/ui/src/app/main.tsx create mode 100644 console/ui/src/app/redux/slices/projectSlice/projectSelectors.ts create mode 100644 console/ui/src/app/redux/slices/projectSlice/projectSlice.ts create mode 100644 console/ui/src/app/redux/store/hooks.ts create mode 100644 console/ui/src/app/redux/store/store.ts create mode 100644 console/ui/src/app/router/PrivateRouterWrapper.tsx create mode 100644 console/ui/src/app/router/Router.tsx create mode 100644 console/ui/src/app/router/routerConfig/ClustersRoutes.tsx create mode 100644 console/ui/src/app/router/routerConfig/OperationsRoutes.tsx create mode 100644 console/ui/src/app/router/routerConfig/SettingsRoutes.tsx create mode 100644 console/ui/src/app/router/routerPathsConfig/index.ts create mode 100644 console/ui/src/app/router/routerPathsConfig/routerClustersPathsConfig.ts create mode 100644 console/ui/src/app/router/routerPathsConfig/routerOperationsPathsConfig.ts create mode 100644 console/ui/src/app/router/routerPathsConfig/routerSettingsPathsConfig.ts create mode 100644 console/ui/src/app/vite-env.d.ts create mode 100644 console/ui/src/entities/authentification-method-form-block/index.ts create mode 100644 console/ui/src/entities/authentification-method-form-block/model/constants.ts create mode 100644 console/ui/src/entities/authentification-method-form-block/ui/AuthenticationFormPart.tsx create mode 100644 console/ui/src/entities/authentification-method-form-block/ui/PasswordMethodFormPart.tsx create mode 100644 console/ui/src/entities/authentification-method-form-block/ui/SshMethodFormPart.tsx create mode 100644 console/ui/src/entities/authentification-method-form-block/ui/index.tsx create mode 100644 console/ui/src/entities/breadcumb-item/index.ts create mode 100644 console/ui/src/entities/breadcumb-item/model/types.ts create mode 100644 console/ui/src/entities/breadcumb-item/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-cloud-provider-block/index.ts create mode 100644 console/ui/src/entities/cluster-cloud-provider-block/model/types.ts create mode 100644 console/ui/src/entities/cluster-cloud-provider-block/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-description-block/index.ts create mode 100644 console/ui/src/entities/cluster-description-block/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/assets/aws.svg create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/assets/azure.svg create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/assets/digitalocean.svg create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/assets/gcp.svg create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/assets/hetzner.svg create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/index.ts create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/lib/hooks.tsx create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/model/types.ts create mode 100644 console/ui/src/entities/cluster-form-cloud-region-block/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-form-cluster-name-block/index.ts create mode 100644 console/ui/src/entities/cluster-form-cluster-name-block/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-form-environment-block/index.ts create mode 100644 console/ui/src/entities/cluster-form-environment-block/model/types.ts create mode 100644 console/ui/src/entities/cluster-form-environment-block/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-form-instances-amount-block/assets/instancesIcon.svg create mode 100644 console/ui/src/entities/cluster-form-instances-amount-block/index.ts create mode 100644 console/ui/src/entities/cluster-form-instances-amount-block/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-form-instances-block/index.ts create mode 100644 console/ui/src/entities/cluster-form-instances-block/model/types.ts create mode 100644 console/ui/src/entities/cluster-form-instances-block/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-info/index.ts create mode 100644 console/ui/src/entities/cluster-info/lib/hooks.tsx create mode 100644 console/ui/src/entities/cluster-info/model/types.ts create mode 100644 console/ui/src/entities/cluster-info/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-instance-config-box/index.ts create mode 100644 console/ui/src/entities/cluster-instance-config-box/model/types.ts create mode 100644 console/ui/src/entities/cluster-instance-config-box/ui/index.tsx create mode 100644 console/ui/src/entities/cluster-name-description-block/index.ts create mode 100644 console/ui/src/entities/cluster-name-description-block/ui/index.tsx create mode 100644 console/ui/src/entities/connection-info/assets/eyeIcon.svg create mode 100644 console/ui/src/entities/connection-info/index.ts create mode 100644 console/ui/src/entities/connection-info/lib/hooks.tsx create mode 100644 console/ui/src/entities/connection-info/model/types.ts create mode 100644 console/ui/src/entities/connection-info/ui/ConnectionInfoRowConteiner.tsx create mode 100644 console/ui/src/entities/connection-info/ui/index.tsx create mode 100644 console/ui/src/entities/database-servers-block/index.ts create mode 100644 console/ui/src/entities/database-servers-block/model/types.ts create mode 100644 console/ui/src/entities/database-servers-block/ui/DatabaseServerBox.tsx create mode 100644 console/ui/src/entities/database-servers-block/ui/index.tsx create mode 100644 console/ui/src/entities/load-balancers-block/index.ts create mode 100644 console/ui/src/entities/load-balancers-block/ui/index.tsx create mode 100644 console/ui/src/entities/postgres-version-block/index.ts create mode 100644 console/ui/src/entities/postgres-version-block/model/types.ts create mode 100644 console/ui/src/entities/postgres-version-block/ui/index.tsx create mode 100644 console/ui/src/entities/providers-block/index.ts create mode 100644 console/ui/src/entities/providers-block/model/types.ts create mode 100644 console/ui/src/entities/providers-block/ui/ClusterFormCloudProviderBox.tsx create mode 100644 console/ui/src/entities/providers-block/ui/index.tsx create mode 100644 console/ui/src/entities/secret-form-block/index.ts create mode 100644 console/ui/src/entities/secret-form-block/lib/functions.ts create mode 100644 console/ui/src/entities/secret-form-block/model/constants.ts create mode 100644 console/ui/src/entities/secret-form-block/model/types.ts create mode 100644 console/ui/src/entities/secret-form-block/ui/AwsSecret.tsx create mode 100644 console/ui/src/entities/secret-form-block/ui/AzureSecret.tsx create mode 100644 console/ui/src/entities/secret-form-block/ui/DigitalOceanSecret.tsx create mode 100644 console/ui/src/entities/secret-form-block/ui/GcpSecret.tsx create mode 100644 console/ui/src/entities/secret-form-block/ui/HetznerSecret.tsx create mode 100644 console/ui/src/entities/secret-form-block/ui/PasswordSecret.tsx create mode 100644 console/ui/src/entities/secret-form-block/ui/SshKeySecret.tsx create mode 100644 console/ui/src/entities/secret-form-block/ui/index.tsx create mode 100644 console/ui/src/entities/settings-proxy-block/index.ts create mode 100644 console/ui/src/entities/settings-proxy-block/model/constants.ts create mode 100644 console/ui/src/entities/settings-proxy-block/model/types.ts create mode 100644 console/ui/src/entities/settings-proxy-block/ui/index.tsx create mode 100644 console/ui/src/entities/sidebar-item/index.ts create mode 100644 console/ui/src/entities/sidebar-item/model/types.ts create mode 100644 console/ui/src/entities/sidebar-item/ui/SidebarItemContent.tsx create mode 100644 console/ui/src/entities/sidebar-item/ui/index.tsx create mode 100644 console/ui/src/entities/ssh-key-block/index.ts create mode 100644 console/ui/src/entities/ssh-key-block/ui/index.tsx create mode 100644 console/ui/src/entities/storage-block/index.ts create mode 100644 console/ui/src/entities/storage-block/lib/functions.ts create mode 100644 console/ui/src/entities/storage-block/ui/index.tsx create mode 100644 console/ui/src/entities/vip-address-block/index.ts create mode 100644 console/ui/src/entities/vip-address-block/ui/index.tsx create mode 100644 console/ui/src/features/add-environment/index.ts create mode 100644 console/ui/src/features/add-environment/ui/index.tsx create mode 100644 console/ui/src/features/add-project/index.ts create mode 100644 console/ui/src/features/add-project/model/constants.ts create mode 100644 console/ui/src/features/add-project/model/types.ts create mode 100644 console/ui/src/features/add-project/model/validation.ts create mode 100644 console/ui/src/features/add-project/ui/index.tsx create mode 100644 console/ui/src/features/add-secret/index.ts create mode 100644 console/ui/src/features/add-secret/model/constants.ts create mode 100644 console/ui/src/features/add-secret/model/types.ts create mode 100644 console/ui/src/features/add-secret/model/validation.ts create mode 100644 console/ui/src/features/add-secret/ui/index.tsx create mode 100644 console/ui/src/features/bradcrumbs/hooks/useBreadcrumbs.tsx create mode 100644 console/ui/src/features/bradcrumbs/index.ts create mode 100644 console/ui/src/features/bradcrumbs/ui/index.tsx create mode 100644 console/ui/src/features/cluster-secret-modal/index.ts create mode 100644 console/ui/src/features/cluster-secret-modal/lib/functions.ts create mode 100644 console/ui/src/features/cluster-secret-modal/model/constants.ts create mode 100644 console/ui/src/features/cluster-secret-modal/model/types.ts create mode 100644 console/ui/src/features/cluster-secret-modal/model/validation.ts create mode 100644 console/ui/src/features/cluster-secret-modal/ui/index.tsx create mode 100644 console/ui/src/features/clusters-overview-table-row-actions/index.ts create mode 100644 console/ui/src/features/clusters-overview-table-row-actions/ui/index.tsx create mode 100644 console/ui/src/features/clusters-table-buttons/index.ts create mode 100644 console/ui/src/features/clusters-table-buttons/model/types.ts create mode 100644 console/ui/src/features/clusters-table-buttons/ui/index.tsx create mode 100644 console/ui/src/features/clusters-table-row-actions/index.ts create mode 100644 console/ui/src/features/clusters-table-row-actions/model/types.ts create mode 100644 console/ui/src/features/clusters-table-row-actions/ui/ClusterTableRemoveButton.tsx create mode 100644 console/ui/src/features/clusters-table-row-actions/ui/index.tsx create mode 100644 console/ui/src/features/environments-table-row-actions/ui/index.tsx create mode 100644 console/ui/src/features/logout-button/index.ts create mode 100644 console/ui/src/features/logout-button/ui/index.tsx create mode 100644 console/ui/src/features/operations-table-buttons/index.ts create mode 100644 console/ui/src/features/operations-table-buttons/lib/functions.ts create mode 100644 console/ui/src/features/operations-table-buttons/model/constants.ts create mode 100644 console/ui/src/features/operations-table-buttons/model/types.ts create mode 100644 console/ui/src/features/operations-table-buttons/ui/index.tsx create mode 100644 console/ui/src/features/operations-table-row-actions/index.ts create mode 100644 console/ui/src/features/operations-table-row-actions/ui/index.tsx create mode 100644 console/ui/src/features/pojects-table-row-actions/index.ts create mode 100644 console/ui/src/features/pojects-table-row-actions/ui/index.tsx create mode 100644 console/ui/src/features/settings-table-buttons/index.ts create mode 100644 console/ui/src/features/settings-table-buttons/lib/functions.ts create mode 100644 console/ui/src/features/settings-table-buttons/ui/index.tsx create mode 100644 console/ui/src/features/settings-table-row-actions/index.ts create mode 100644 console/ui/src/features/settings-table-row-actions/model/constants.ts create mode 100644 console/ui/src/features/settings-table-row-actions/ui/index.tsx create mode 100644 console/ui/src/pages/404/index.ts create mode 100644 console/ui/src/pages/404/ui/illustration.tsx create mode 100644 console/ui/src/pages/404/ui/index.tsx create mode 100644 console/ui/src/pages/add-cluster/index.ts create mode 100644 console/ui/src/pages/add-cluster/ui/index.tsx create mode 100644 console/ui/src/pages/clusters/index.ts create mode 100644 console/ui/src/pages/clusters/ui/index.tsx create mode 100644 console/ui/src/pages/login/index.ts create mode 100644 console/ui/src/pages/login/model/constants.ts create mode 100644 console/ui/src/pages/login/model/types.ts create mode 100644 console/ui/src/pages/login/ui/index.tsx create mode 100644 console/ui/src/pages/operation-log/index.ts create mode 100644 console/ui/src/pages/operation-log/ui/index.tsx create mode 100644 console/ui/src/pages/operations/index.ts create mode 100644 console/ui/src/pages/operations/ui/index.tsx create mode 100644 console/ui/src/pages/overview-cluster/index.ts create mode 100644 console/ui/src/pages/overview-cluster/ui/index.tsx create mode 100644 console/ui/src/pages/settings/index.ts create mode 100644 console/ui/src/pages/settings/model/constants.ts create mode 100644 console/ui/src/pages/settings/ui/index.tsx create mode 100644 console/ui/src/shared/api/api/clusters.ts create mode 100644 console/ui/src/shared/api/api/deployments.ts create mode 100644 console/ui/src/shared/api/api/environments.ts create mode 100644 console/ui/src/shared/api/api/operations.ts create mode 100644 console/ui/src/shared/api/api/other.ts create mode 100644 console/ui/src/shared/api/api/projects.ts create mode 100644 console/ui/src/shared/api/api/secrets.ts create mode 100644 console/ui/src/shared/api/api/settings.ts create mode 100644 console/ui/src/shared/api/apiConfig.ts create mode 100644 console/ui/src/shared/api/baseApi.ts create mode 100644 console/ui/src/shared/api/enhancedSecretsApi.ts create mode 100644 console/ui/src/shared/assets/PGCLogo.svg create mode 100644 console/ui/src/shared/assets/calendarClockICon.svg create mode 100644 console/ui/src/shared/assets/checkIcon.svg create mode 100644 console/ui/src/shared/assets/clustersIcon.svg create mode 100644 console/ui/src/shared/assets/collapseIcon.svg create mode 100644 console/ui/src/shared/assets/cpuIcon.svg create mode 100644 console/ui/src/shared/assets/databaseIcon.svg create mode 100644 console/ui/src/shared/assets/docsIcon.svg create mode 100644 console/ui/src/shared/assets/flagIcon.svg create mode 100644 console/ui/src/shared/assets/githubIcon.svg create mode 100644 console/ui/src/shared/assets/instanceIcon.svg create mode 100644 console/ui/src/shared/assets/lanIcon.svg create mode 100644 console/ui/src/shared/assets/logoutIcon.svg create mode 100644 console/ui/src/shared/assets/memoryIcon.svg create mode 100644 console/ui/src/shared/assets/operationsIcon.svg create mode 100644 console/ui/src/shared/assets/ramIcon.svg create mode 100644 console/ui/src/shared/assets/serversIcon.svg create mode 100644 console/ui/src/shared/assets/settingsIcon.svg create mode 100644 console/ui/src/shared/assets/sponsorIcon.svg create mode 100644 console/ui/src/shared/assets/storageIcon.svg create mode 100644 console/ui/src/shared/assets/supportIcon.svg create mode 100644 console/ui/src/shared/config/constants.ts create mode 100644 console/ui/src/shared/i18n/i18n.ts create mode 100644 console/ui/src/shared/i18n/locales/en/clusters.json create mode 100644 console/ui/src/shared/i18n/locales/en/operations.json create mode 100644 console/ui/src/shared/i18n/locales/en/settings.json create mode 100644 console/ui/src/shared/i18n/locales/en/shared.json create mode 100644 console/ui/src/shared/i18n/locales/en/toasts.json create mode 100644 console/ui/src/shared/i18n/locales/en/validation.json create mode 100644 console/ui/src/shared/lib/functions.ts create mode 100644 console/ui/src/shared/lib/hooks.tsx create mode 100644 console/ui/src/shared/model/constants.ts create mode 100644 console/ui/src/shared/model/types.ts create mode 100644 console/ui/src/shared/theme/theme.ts create mode 100644 console/ui/src/shared/ui/copy-icon/assets/copyIcon.svg create mode 100644 console/ui/src/shared/ui/copy-icon/index.ts create mode 100644 console/ui/src/shared/ui/copy-icon/model/types.ts create mode 100644 console/ui/src/shared/ui/copy-icon/ui/index.tsx create mode 100644 console/ui/src/shared/ui/default-form-buttons/index.ts create mode 100644 console/ui/src/shared/ui/default-form-buttons/model/types.ts create mode 100644 console/ui/src/shared/ui/default-form-buttons/ui/index.tsx create mode 100644 console/ui/src/shared/ui/default-table/index.ts create mode 100644 console/ui/src/shared/ui/default-table/ui/index.tsx create mode 100644 console/ui/src/shared/ui/info-card-body/index.ts create mode 100644 console/ui/src/shared/ui/info-card-body/model/types.ts create mode 100644 console/ui/src/shared/ui/info-card-body/ui/index.tsx create mode 100644 console/ui/src/shared/ui/selectable-box/index.ts create mode 100644 console/ui/src/shared/ui/selectable-box/model/types.ts create mode 100644 console/ui/src/shared/ui/selectable-box/ui/index.tsx create mode 100644 console/ui/src/shared/ui/settings-add-entity/model/constants.ts create mode 100644 console/ui/src/shared/ui/settings-add-entity/model/types.ts create mode 100644 console/ui/src/shared/ui/settings-add-entity/model/validation.ts create mode 100644 console/ui/src/shared/ui/settings-add-entity/ui/index.tsx create mode 100644 console/ui/src/shared/ui/slider-box/index.ts create mode 100644 console/ui/src/shared/ui/slider-box/lib/functions.ts create mode 100644 console/ui/src/shared/ui/slider-box/model/types.ts create mode 100644 console/ui/src/shared/ui/slider-box/ui/index.tsx create mode 100644 console/ui/src/shared/ui/spinner/index.ts create mode 100644 console/ui/src/shared/ui/spinner/ui/index.tsx create mode 100644 console/ui/src/widgets/cluster-form/index.ts create mode 100644 console/ui/src/widgets/cluster-form/model/constants.ts create mode 100644 console/ui/src/widgets/cluster-form/model/types.ts create mode 100644 console/ui/src/widgets/cluster-form/model/validation.ts create mode 100644 console/ui/src/widgets/cluster-form/ui/ClusterFormCloudProviderFormPart.tsx create mode 100644 console/ui/src/widgets/cluster-form/ui/ClusterFormLocalMachineFormPart.tsx create mode 100644 console/ui/src/widgets/cluster-form/ui/ClusterFormRegionConfigBox.tsx create mode 100644 console/ui/src/widgets/cluster-form/ui/index.tsx create mode 100644 console/ui/src/widgets/cluster-overview-table/index.ts create mode 100644 console/ui/src/widgets/cluster-overview-table/lib/hooks.tsx create mode 100644 console/ui/src/widgets/cluster-overview-table/model/constants.ts create mode 100644 console/ui/src/widgets/cluster-overview-table/model/types.ts create mode 100644 console/ui/src/widgets/cluster-overview-table/ui/ClustersOverviewTableButtons.tsx create mode 100644 console/ui/src/widgets/cluster-overview-table/ui/index.tsx create mode 100644 console/ui/src/widgets/cluster-summary/assets/awsIcon.svg create mode 100644 console/ui/src/widgets/cluster-summary/assets/azureIcon.svg create mode 100644 console/ui/src/widgets/cluster-summary/assets/digitaloceanIcon.svg create mode 100644 console/ui/src/widgets/cluster-summary/assets/gcpIcon.svg create mode 100644 console/ui/src/widgets/cluster-summary/assets/hetznerIcon.svg create mode 100644 console/ui/src/widgets/cluster-summary/assets/hetznerIcon2.svg create mode 100644 console/ui/src/widgets/cluster-summary/index.ts create mode 100644 console/ui/src/widgets/cluster-summary/lib/hooks.tsx create mode 100644 console/ui/src/widgets/cluster-summary/model/constants.ts create mode 100644 console/ui/src/widgets/cluster-summary/model/types.ts create mode 100644 console/ui/src/widgets/cluster-summary/ui/index.tsx create mode 100644 console/ui/src/widgets/clusters-table/assets/correctIcon.svg create mode 100644 console/ui/src/widgets/clusters-table/assets/errorIcon.svg create mode 100644 console/ui/src/widgets/clusters-table/assets/noClustersIcon.svg create mode 100644 console/ui/src/widgets/clusters-table/assets/warningIcon.svg create mode 100644 console/ui/src/widgets/clusters-table/index.ts create mode 100644 console/ui/src/widgets/clusters-table/lib/functions.ts create mode 100644 console/ui/src/widgets/clusters-table/lib/hooks.tsx create mode 100644 console/ui/src/widgets/clusters-table/model/constants.ts create mode 100644 console/ui/src/widgets/clusters-table/model/types.ts create mode 100644 console/ui/src/widgets/clusters-table/ui/ClustersEmptyRowsFallback.tsx create mode 100644 console/ui/src/widgets/clusters-table/ui/index.tsx create mode 100644 console/ui/src/widgets/environments-table/index.ts create mode 100644 console/ui/src/widgets/environments-table/lib/hooks.tsx create mode 100644 console/ui/src/widgets/environments-table/model/constants.ts create mode 100644 console/ui/src/widgets/environments-table/model/types.ts create mode 100644 console/ui/src/widgets/environments-table/ui/EnvironmentsTableButtons.tsx create mode 100644 console/ui/src/widgets/environments-table/ui/index.tsx create mode 100644 console/ui/src/widgets/header/index.ts create mode 100644 console/ui/src/widgets/header/ui/index.tsx create mode 100644 console/ui/src/widgets/main/index.ts create mode 100644 console/ui/src/widgets/main/ui/index.tsx create mode 100644 console/ui/src/widgets/operations-table/index.ts create mode 100644 console/ui/src/widgets/operations-table/lib/hooks.tsx create mode 100644 console/ui/src/widgets/operations-table/model/constants.ts create mode 100644 console/ui/src/widgets/operations-table/model/types.ts create mode 100644 console/ui/src/widgets/operations-table/ui/index.tsx create mode 100644 console/ui/src/widgets/projects-table/index.tsx create mode 100644 console/ui/src/widgets/projects-table/lib/hooks.tsx create mode 100644 console/ui/src/widgets/projects-table/model/constants.ts create mode 100644 console/ui/src/widgets/projects-table/model/types.ts create mode 100644 console/ui/src/widgets/projects-table/ui/ProjectsTableButtons.tsx create mode 100644 console/ui/src/widgets/projects-table/ui/index.tsx create mode 100644 console/ui/src/widgets/secrets-table/index.ts create mode 100644 console/ui/src/widgets/secrets-table/lib/hooks.tsx create mode 100644 console/ui/src/widgets/secrets-table/model/constants.ts create mode 100644 console/ui/src/widgets/secrets-table/model/types.ts create mode 100644 console/ui/src/widgets/secrets-table/ui/index.tsx create mode 100644 console/ui/src/widgets/settings-form/index.ts create mode 100644 console/ui/src/widgets/settings-form/ui/index.tsx create mode 100644 console/ui/src/widgets/sidebar/index.ts create mode 100644 console/ui/src/widgets/sidebar/model/constants.ts create mode 100644 console/ui/src/widgets/sidebar/ui/index.tsx create mode 100644 console/ui/tsconfig.json create mode 100644 console/ui/tsconfig.node.json create mode 100644 console/ui/vite.config.mts create mode 100644 console/ui/yarn.lock diff --git a/console/service/Dockerfile b/console/service/Dockerfile new file mode 100644 index 000000000..e6e1320db --- /dev/null +++ b/console/service/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.22.3-bookworm as builder +WORKDIR /go/src/pg-console + +COPY . . + +RUN make build_in_docker + +FROM debian:bookworm-slim + +ENV PROJECT_NAME=pg-console +ENV PROJECT_PATH=/go/src/${PROJECT_NAME} + +COPY --from=builder ${PROJECT_PATH}/${PROJECT_NAME} /usr/local/bin/ +COPY --from=builder ${PROJECT_PATH}/db/migrations /etc/db/migrations + +CMD ${PROJECT_NAME} diff --git a/console/service/Makefile b/console/service/Makefile new file mode 100644 index 000000000..148ff4944 --- /dev/null +++ b/console/service/Makefile @@ -0,0 +1,31 @@ +ifndef GO_BIN +override GO_BIN = "pg-console" +endif + +APP = main.go + +swagger_install: + { \ + export my_dir=$$(pwd) ;\ + export dir=$$(mktemp -d) ;\ + retry_count=0 ;\ + max_retries=5 ;\ + until [ "$$retry_count" -ge "$$max_retries" ]; do \ + git clone https://github.com/go-swagger/go-swagger "$$dir" && break ;\ + retry_count=$$((retry_count+1)) ;\ + echo "Retry $$retry_count/$$max_retries" ;\ + sleep 1 ;\ + done ;\ + cd "$$dir" ;\ + go install ./cmd/swagger ;\ + cd "$$my_dir" ;\ + swagger version ;\ + } + +build: ## Build app + @go build -o $(GO_BIN) $(APP) + +swagger: + @swagger generate server --name PgConsole --spec api/swagger.yaml --principal interface{} --exclude-main + +build_in_docker: swagger_install swagger build diff --git a/console/service/README.md b/console/service/README.md new file mode 100644 index 000000000..19d35458d --- /dev/null +++ b/console/service/README.md @@ -0,0 +1,104 @@ +# postgesql-cluster-console server + +Server side for postgresql-cluster-console. +REST service that implements API for WEB integration. +Project is written on `golang` and used [swagger](https://github.com/go-swagger/go-swagger) for server-side auto generation. +Server is received requests from WEB for creation and manage clusters. +Under the hood server uses docker for running ansible scripts with cluster deploy logic. + +## Build +Swagger specification is used for creating server REST API. First of all you need to install swagger tool to build auto generated go-files. +``` +export dir=$$(mktemp -d) +git clone https://github.com/go-swagger/go-swagger "$$dir" +cd "$$dir" +go install ./cmd/swagger +``` +Then you need to generate server side files: +``` +swagger generate server --name DbConsole --spec api/swagger.yaml --principal interface{} --exclude-main +``` + +After that you can build server with following command: +``` +go build -o pg-console main.go +``` + +The project also contains makefile with all commands. So you can just do next steps: +``` +make swagger_install +make swagger +make build +``` + +## Configuration +Server is configured via the environment. The following environment variables can be used: +``` +KEY TYPE DEFAULT REQUIRED DESCRIPTION +PG_CONSOLE_LOGGER_LEVEL String DEBUG Log level. Accepted values: [TRACE, DEBUG, INFO, WARN, ERROR, FATAL, PANIC] +PG_CONSOLE_HTTP_HOST String 0.0.0.0 Accepted host for connection. '0.0.0.0' for all hosts +PG_CONSOLE_HTTP_PORT Integer 8080 Listening port +PG_CONSOLE_HTTP_WRITETIMEOUT Duration 10s Maximum duration before timing out write of the response +PG_CONSOLE_HTTP_READTIMEOUT Duration 10s Maximum duration before timing out read of the request +PG_CONSOLE_HTTPS_ISUSED True or False false Flag for turn on/off https +PG_CONSOLE_HTTPS_HOST String 0.0.0.0 Accepted host for connection. '0.0.0.0' for all hosts +PG_CONSOLE_HTTPS_PORT Integer 8081 Listening port +PG_CONSOLE_HTTPS_CACERT String /etc/pg_console/cacert.pem The certificate to use for secure connections +PG_CONSOLE_HTTPS_SERVERCERT String /etc/pg_console/server-cert.pem The certificate authority file to be used with mutual tls auth +PG_CONSOLE_HTTPS_SERVERKEY String /etc/pg_console/server-key.pem The private key to use for secure connections +PG_CONSOLE_AUTHORIZATION_TOKEN String auth_token Authorization token for REST API +PG_CONSOLE_DB_HOST String localhost Database host +PG_CONSOLE_DB_PORT Unsigned Integer 5432 Database port +PG_CONSOLE_DB_DBNAME String postgres Database name +PG_CONSOLE_DB_USER String postgres Database user name +PG_CONSOLE_DB_PASSWORD String postgres-pass Database user password +PG_CONSOLE_DB_MAXCONNS Integer 10 MaxConns is the maximum size of the pool +PG_CONSOLE_DB_MAXCONNLIFETIME Duration 60s MaxConnLifetime is the duration since creation after which a connection will be automatically closed +PG_CONSOLE_DB_MAXCONNIDLETIME Duration 60s MaxConnIdleTime is the duration after which an idle connection will be automatically closed by the health check +PG_CONSOLE_DB_MIGRATIONDIR String /etc/db/migrations Path to directory with migration scripts +PG_CONSOLE_ENCRYPTIONKEY String super_secret Encryption key for secret storage +PG_CONSOLE_DOCKER_HOST String unix:///var/run/docker.sock Docker host +PG_CONSOLE_DOCKER_LOGDIR String /tmp/ansible Directory inside docker container for ansible json log +PG_CONSOLE_DOCKER_IMAGE String vitabaks/postgresql_cluster:cloud Docker image for postgresql_cluster +PG_CONSOLE_LOGWATCHER_RUNEVERY Duration 1m LogWatcher run interval +PG_CONSOLE_LOGWATCHER_ANALYZEPAST Duration 48h LogWatcher gets operations to analyze which created_at > now() - AnalyzePast +PG_CONSOLE_CLUSTERWATCHER_RUNEVERY Duration 1m ClusterWatcher run interval +PG_CONSOLE_CLUSTERWATCHER_POOLSIZE Integer 4 Amount of async request from ClusterWatcher +``` + +## Project architecture +``` +|-api - swagger specification +|-internal - folder with all internal logic +| |-controllers - REST fuctions and basic logic for handlers +| | |-cluster - REST API for clusters objects +| | |-dictionary - REST API for dictionaries objects +| | |-operation - REST API for operations objects +| | |-project - REST API for projects objects +| | |-secret - REST API for secrets objects +| |-convert - functions for convert DB model for REST model +| |-db - base DB functions +| |-service - common logic for aggrigation all server logic +| |-storage - DB logic +| |-watcher - async watchers +| | |-log_collector.go - collecting logs from running docker container +| | |-log_watcher.go - JSON container log parser +| | |-server_watcher.go - collecting servers statuses +| |-xdocker - basic logic for docker +|-middleware - common REST middlewares for server +|-migrations - DB migrations logic +|-pkg - folder with common logic +| |-patroni - client for patroni integration +| |-tracer - base structure for tracing +|-*models - auto-generated files with REST models +|-*restapi - auto-generated files with REST server +|-main.go - entry point +``` + +## Secrets +Server handles different kind of secrets, such as: +* cloud secrets, that uses for cloud connections +* ssh keys and passwords for connection to owm machine servers +* database secrets + +Be attention to use `TRACE` level of logging. With `TRACE` level some kind of secrets can be present in logs. \ No newline at end of file diff --git a/console/service/VERSION b/console/service/VERSION new file mode 100644 index 000000000..afaf360d3 --- /dev/null +++ b/console/service/VERSION @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/console/service/api/swagger.yaml b/console/service/api/swagger.yaml new file mode 100644 index 000000000..89acb093f --- /dev/null +++ b/console/service/api/swagger.yaml @@ -0,0 +1,1663 @@ +swagger: '2.0' +info: + title: PG Console + description: API for PG Console WEB + version: 1.0.0 +host: localhost:8080 +schemes: + - http +produces: + - application/json +consumes: + - application/json +basePath: "/api/v1" + +paths: + /version: + get: + summary: Get version of server + tags: + - system + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.Version" + + /external/deployments: + get: + summary: Get full info about available external deployments + tags: + - dictionary + parameters: + - name: offset + in: query + required: false + type: integer + - name: limit + in: query + required: false + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.DeploymentsInfo" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /database/extensions: + get: + summary: "Info about available database extensions" + tags: + - dictionary + parameters: + - name: offset + in: query + required: false + type: integer + - name: limit + in: query + required: false + type: integer + - name: extension_type + in: query + required: false + type: string + default: "all" + enum: + - "all" + - "contrib" + - "third_party" + - name: postgres_version + in: query + required: false + type: string + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.DatabaseExtensions" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /environments: + get: + summary: "Get environments list" + tags: + - environment + parameters: + - name: limit + in: query + required: false + type: integer + - name: offset + in: query + required: false + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.EnvironmentsList" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + post: + summary: "Create environment" + tags: + - environment + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.Environment' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.Environment" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /environments/{id}: + delete: + summary: "Delete environment" + tags: + - environment + parameters: + - name: id + in: path + required: true + type: integer + responses: + '204': + description: OK + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /postgres_versions: + get: + summary: "Get supported postgres versions" + tags: + - dictionary + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.PostgresVersions" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /settings: + post: + summary: "Create new setting" + tags: + - setting + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.CreateSetting' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.Setting" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + get: + summary: "Get settings" + tags: + - setting + parameters: + - name: name + in: query + required: false + type: string + description: "Filter by name" + - name: offset + in: query + required: false + type: integer + - name: limit + in: query + required: false + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.Settings" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /settings/{name}: + patch: + summary: "Changed setting" + tags: + - setting + parameters: + - name: name + in: path + type: string + required: true + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ChangeSetting' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.Setting" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /clusters: + post: + summary: "Create new cluster" + tags: + - cluster + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ClusterCreate' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClusterCreate" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + get: + summary: "Get info about clusters" + tags: + - cluster + parameters: + - name: offset + in: query + required: false + type: integer + - name: limit + in: query + required: false + type: integer + - name: project_id + in: query + required: true + type: integer + - name: name + in: query + required: false + type: string + description: "Filter by name" + - name: status + type: string + in: query + required: false + description: "Filter by status" + - name: location + type: string + in: query + required: false + description: "Filter by location" + - name: environment + type: string + in: query + required: false + description: "Filter by environment" + - name: server_count + type: integer + in: query + required: false + description: "Filter by server_count" + - name: postgres_version + type: integer + in: query + required: false + description: "Filter by postgres_version" + - name: created_at_from + required: false + type: string + format: date-time + in: query + description: "Created at after this date" + - name: created_at_to + required: false + type: string + format: date-time + in: query + description: "Created at till this date" + - name: sort_by + in: query + required: false + type: string + description: "Sort by fields. Example: sort_by=id,-name,created_at,updated_at\n + Supported values:\n + - id\n + - name\n + - created_at\n + - updated_at\n + - environment\n + - project\n + - status\n + - location\n + - server_count\n + - postgres_version\n" + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClustersInfo" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /clusters/default_name: + get: + summary: "Get cluster default name" + tags: + - cluster + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClusterDefaultName" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /clusters/{id}: + get: + summary: "Get cluster info" + tags: + - cluster + parameters: + - name: id + in: path + required: true + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/ClusterInfo" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + delete: + summary: "Delete cluster" + tags: + - cluster + parameters: + - name: id + in: path + required: true + type: integer + responses: + '204': + description: OK + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /servers/{id}: + delete: + summary: "Delete server from cluster" + tags: + - cluster + parameters: + - name: id + in: path + required: true + type: integer + responses: + '204': + description: OK + headers: + x-cluster-id: + type: integer + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /clusters/{id}/refresh: + post: + summary: "Refresh cluster info" + tags: + - cluster + parameters: + - name: id + in: path + required: true + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/ClusterInfo" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + + # TODO: not implemented yet + /clusters/{id}/reinit: + post: + summary: "Reinit cluster" + deprecated: true + tags: + - cluster + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ClusterReinit' + - name: id + in: path + required: true + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClusterCreate" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + # TODO: not implemented yet + /clusters/{id}/reload: + post: + summary: "Reload cluster" + deprecated: true + tags: + - cluster + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ClusterReload' + - name: id + in: path + required: true + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClusterCreate" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + # TODO: not implemented yet + /clusters/{id}/restart: + post: + summary: "Restart cluster" + deprecated: true + tags: + - cluster + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ClusterRestart' + - name: id + in: path + required: true + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClusterCreate" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + # TODO: not implemented yet + /clusters/{id}/stop: + post: + summary: "Stop cluster" + deprecated: true + tags: + - cluster + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ClusterStop' + - name: id + in: path + required: true + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClusterCreate" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + # TODO: not implemented yet + /clusters/{id}/start: + post: + summary: "Start cluster" + deprecated: true + tags: + - cluster + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ClusterStart' + - name: id + in: path + required: true + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ClusterCreate" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + # TODO: not implemented yet + /clusters/{id}/remove: + post: + summary: "Remove cluster" + deprecated: true + tags: + - cluster + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ClusterRemove' + - name: id + in: path + required: true + type: integer + responses: + '204': + description: OK + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /projects: + post: + summary: "Create new project" + tags: + - project + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ProjectCreate' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.Project" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + get: + summary: "Get projects list" + tags: + - project + parameters: + - name: limit + in: query + required: false + type: integer + - name: offset + in: query + required: false + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.ProjectsList" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /projects/{id}: + patch: + summary: "Change project" + tags: + - project + parameters: + - name: id + in: path + required: true + type: integer + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.ProjectPatch' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.Project" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + delete: + summary: "Delete project" + tags: + - project + parameters: + - name: id + in: path + required: true + type: integer + responses: + '204': + description: OK + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /secrets: + post: + summary: "Create new secret" + tags: + - secret + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.SecretCreate' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.SecretInfo" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + get: + summary: "Get secrets list" + tags: + - secret + parameters: + - name: limit + in: query + required: false + type: integer + - name: offset + in: query + required: false + type: integer + - name: project_id + in: query + required: true + type: integer + - name: name + in: query + required: false + type: string + description: "Filter by name" + - name: type + in: query + required: false + type: string + description: "Filter by type" + - name: sort_by + in: query + required: false + type: string + description: "Sort by fields. Example: sort_by=id,name,-type,created_at,updated_at" + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.SecretInfoList" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /secrets/{id}: + patch: + summary: "Change secret" + tags: + - secret + parameters: + - name: id + in: path + required: true + type: integer + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Request.SecretPatch' + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.SecretInfo" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + delete: + summary: "Delete secret" + tags: + - secret + parameters: + - name: id + in: path + required: true + type: integer + responses: + '204': + description: OK + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /operations: + get: + summary: "Get operations list for current project" + tags: + - operation + parameters: + - name: project_id + in: query + required: true + type: integer + description: "Required parameter for filter" + - name: start_date + required: true + type: string + format: date-time + in: query + description: "Operations started after this date" + - name: end_date + required: true + type: string + format: date-time + in: query + description: "Operations started till this date" + - name: cluster_name + in: query + required: false + type: string + description: "Filter by cluster_name" + - name: type + in: query + required: false + type: string + description: "Filter by type" + - name: status + in: query + required: false + type: string + description: "Filter by status" + - name: environment + in: query + required: false + type: string + description: "Filter by environment" + - name: sort_by + in: query + required: false + type: string + description: "Sort by fields. Example: sort_by=cluster_name,-type,status,id,created_at,updated_at\n + Supported valuese:\n + - id\n + - cluster_name\n + - type\n + - status\n + - started_at\n + - updated_at\n + - cluster\n + - environment\n" + - name: limit + in: query + required: false + type: integer + - name: offset + in: query + required: false + type: integer + responses: + '200': + description: OK + schema: + $ref: "#/definitions/Response.OperationsList" + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + + /operations/{id}/log: + get: + summary: "Get operation log by operation_id" + tags: + - operation + consumes: + - plain/text + parameters: + - name: id + in: path + required: true + type: integer + description: "Operation id" + responses: + '200': + description: OK + schema: + type: string + headers: + content-type: + type: string + x-log-completed: + type: boolean + '400': + description: Error + schema: + $ref: "#/definitions/Response.Error" + +definitions: + Response.Version: + title: Version response + type: object + properties: + version: + type: string + example: v1.0.0 + + Response.Error: + title: Error object + type: object + properties: + code: + type: integer + title: + type: string + description: + type: string + + Meta.Pagination: + title: Pagination info for list requests + type: object + properties: + offset: + type: integer + x-nullable: true + limit: + type: integer + x-nullable: true + count: + type: integer + x-nullable: true + + Response.DeploymentsInfo: + title: Deployments info + type: object + properties: + data: + type: array + items: + $ref: "#/definitions/Response.DeploymentInfo" + meta: + $ref: '#/definitions/Meta.Pagination' + + Response.DeploymentInfo: + description: Deployment info + type: object + properties: + code: + type: string + example: "aws" + description: + type: string + example: "Amazon web services" + avatar_url: + type: string + cloud_regions: + description: "List of available regions for current deployment" + type: array + items: + $ref: '#/definitions/DeploymentInfo.CloudRegion' + instance_types: + description: "Lists of available instance types" + type: object + properties: + small: + type: array + x-nullable: true + items: + $ref: '#/definitions/Deployment.InstanceType' + medium: + type: array + items: + $ref: '#/definitions/Deployment.InstanceType' + large: + type: array + items: + $ref: '#/definitions/Deployment.InstanceType' + volumes: + type: array + description: "Hardware disks info" + items: + type: object + properties: + volume_type: + type: string + description: "Volume type" + example: "gp3" + volume_description: + type: string + description: "Volume description" + example: "General purpose SSD disk" + min_size: + type: integer + description: "Sets in GB" + example: 10 + max_size: + type: integer + description: "Sets in GB" + example: 256 + price_monthly: + type: number + description: "Price for disk by months" + example: 0.1 + currency: + type: string + description: "Price currency" + example: "$" + is_default: + type: boolean + x-nullable: true + description: "Default volume" + example: false + + DeploymentInfo.CloudRegion: + type: object + properties: + code: + type: string + description: "unique parameter for DB" + example: "north_america" + name: + type: string + description: "Field for web" + example: "North America" + datacenters: + type: array + description: "List of datacenters for this region" + items: + type: object + properties: + code: + type: string + example: "ca-central-1" + location: + type: string + example: "Canada (central)" + cloud_image: + $ref: '#/definitions/Deployment.CloudImage' + + Deployment.CloudImage: + type: object + properties: + image: + type: object + example: '{"server_image": "ami-078b3985bbc361448"}' + arch: + type: string + example: "amd64" + os_name: + type: string + example: "Ubuntu" + os_version: + type: string + example: "22.04 LTS" + updated_at: + type: string + format: datetime + + Deployment.InstanceType: + type: object + properties: + code: + type: string + example: "m5.2xlarge" + cpu: + type: integer + example: 8 + ram: + type: integer + example: 256 + price_hourly: + type: number + description: "Price for 1 instance by hour" + example: 0.01 + price_monthly: + type: number + description: "Price for 1 instance by month" + example: 1.2 + currency: + type: string + description: "Price currency" + example: "$" + + Response.DatabaseExtensions: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/Response.DatabaseExtension' + meta: + $ref: '#/definitions/Meta.Pagination' + + Response.DatabaseExtension: + type: object + description: "Info about database extension" + properties: + name: + type: string + example: "Citus" + description: + type: string + x-nullable: true + example: "Citus is PostgreSQL extension that transforms..." + url: + type: string + x-nullable: true + example: "https://github.com/citusdata/citus" + image: + type: string + x-nullable: true + example: "citus.png" + postgres_min_version: + type: string + x-nullable: true + example: "11" + postgres_max_version: + type: string + x-nullable: true + example: "16" + contrib: + type: boolean + example: false + + Request.ClusterCreate: + type: object + description: "Request struct for cluster creation" + properties: + name: + type: string + example: "drm-prod-pgcluster" + description: + type: string + description: "Info about cluster" + auth_info: + type: object + description: "Info for deployment system authorization" + properties: + secret_id: + type: integer + example: 1 + project_id: + type: integer + description: "Project for new cluster" + environment_id: + type: integer + description: "Project environment" + envs: + type: array + items: + type: string + extra_vars: + type: array + items: + type: string + + Response.ClusterDefaultName: + type: object + description: "Response struct for cluster default name" + properties: + name: + type: string + example: "postgres-cluster-01" + + Response.ClusterCreate: + type: object + description: "Response struct for cluster creation" + properties: + cluster_id: + type: integer + description: "unique code for cluster" + operation_id: + type: integer + description: "operation id" + + Response.ClusterLogs: + type: object + description: "Logs for cluster" + properties: + logs: + type: string + description: "all available logs" + + Response.ClustersInfo: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/ClusterInfo' + meta: + $ref: '#/definitions/Meta.Pagination' + + ClusterInfo: + type: object + description: "Cluster info" + properties: + id: + type: integer + name: + type: string + example: "drm-prod-pgcluster" + description: + type: string + status: + type: string + example: "healthy" + creation_time: + type: string + format: date-time + example: "16.10.2023T11:20:00Z" + environment: + type: string + example: "production" + servers: + type: array + items: + $ref: "#/definitions/ClusterInfo.Instance" + postgres_version: + type: integer + format: int32 + example: 15 + cluster_location: + type: string + description: "Code of location" + example: "eu-north-1" + project_name: + type: string + description: "Project for cluster" + connection_info: + type: object + + ClusterInfo.AdditionalSettings: + type: object + description: "Additional settings for cluster" + properties: + connection_info: + type: object + + ClusterInfo.Instance: + type: object + description: "Instance info for current cluster" + properties: + id: + type: integer + name: + type: string + example: "pgnode1" + ip: + type: string + example: "10.128.64.141" + status: + type: string + role: + type: string + example: "leader" + timeline: + type: integer + format: int64 + example: 1 + x-nullable: true + lag: + type: integer + format: int64 + example: 0 + x-nullable: true + tags: + type: object + pending_restart: + type: boolean + example: false + x-nullable: true + + Request.ClusterReinit: + type: object + description: "Reinit cluster" + + Request.ClusterReload: + type: object + description: "Reload cluster" + + Request.ClusterRestart: + type: object + description: "Restart cluster" + + Request.ClusterStop: + type: object + description: "Stop cluster" + + Request.ClusterStart: + type: object + description: "Start cluster" + + Request.ClusterRemove: + type: object + description: "Remove cluster" + + Request.ProjectCreate: + type: object + properties: + name: + type: string + example: "default" + description: + type: string + example: "Default project" + + Request.ProjectPatch: + type: object + properties: + name: + type: string + x-nullable: true + description: + type: string + x-nullable: true + + Response.Project: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + x-nullable: true + created_at: + type: string + format: date-time + example: "16.10.2023T11:20:00Z" + updated_at: + type: string + format: date-time + x-nullable: true + example: "16.10.2023T11:20:00Z" + + Response.ProjectsList: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/Response.Project' + meta: + type: object + $ref: '#/definitions/Meta.Pagination' + + Request.SecretCreate: + type: object + properties: + project_id: + type: integer + example: 1 + name: + type: string + example: "aws key" + type: + $ref: '#/definitions/Secret.Type' + value: + type: object + $ref: '#/definitions/Request.SecretValue' + + Secret.Type: + type: string + enum: + - "aws" + - "gcp" + - "hetzner" + - "ssh_key" + - "digitalocean" + - "password" + - "azure" + + Request.SecretValue: + type: object + properties: + aws: + type: object + $ref: '#/definitions/Request.SecretValue.Aws' + x-nullable: true + gcp: + type: object + $ref: '#/definitions/Request.SecretValue.Gcp' + x-nullable: true + hetzner: + type: object + $ref: '#/definitions/Request.SecretValue.Hetzner' + x-nullable: true + ssh_key: + type: object + $ref: '#/definitions/Request.SecretValue.SshKey' + x-nullable: true + digitalocean: + type: object + $ref: '#/definitions/Request.SecretValue.DigitalOcean' + x-nullable: true + password: + type: object + $ref: '#/definitions/Request.SecretValue.Password' + x-nullable: true + azure: + type: object + $ref: '#/definitions/Request.SecretValue.Azure' + x-nullable: true + + Request.SecretValue.Aws: + type: object + properties: + AWS_ACCESS_KEY_ID: + type: string + AWS_SECRET_ACCESS_KEY: + type: string + + Request.SecretValue.Gcp: + type: object + properties: + GCP_SERVICE_ACCOUNT_CONTENTS: + type: string + + Request.SecretValue.Hetzner: + type: object + properties: + HCLOUD_API_TOKEN: + type: string + + Request.SecretValue.SshKey: + type: object + properties: + SSH_PRIVATE_KEY: + type: string + + Request.SecretValue.DigitalOcean: + type: object + properties: + DO_API_TOKEN: + type: string + + Request.SecretValue.Password: + type: object + properties: + USERNAME: + type: string + PASSWORD: + type: string + + Request.SecretValue.Azure: + type: object + properties: + AZURE_SUBSCRIPTION_ID: + type: string + AZURE_CLIENT_ID: + type: string + AZURE_SECRET: + type: string + AZURE_TENANT: + type: string + + Request.SecretPatch: + type: object + properties: + name: + type: string + example: "aws key" + x-nullable: true + type: + type: string + example: "aws" + x-nullable: true + value: + type: string + example: "c2VjcmV0" + description: "Secret value in base64" + x-nullable: true + + Response.SecretInfo: + type: object + properties: + id: + type: integer + example: 1 + project_id: + type: integer + example: 1 + name: + type: string + example: "aws key" + type: + $ref: '#/definitions/Secret.Type' + created_at: + type: string + format: date-time + example: "16.10.2023T11:20:00Z" + updated_at: + type: string + format: date-time + x-nullable: true + example: "16.10.2023T11:20:00Z" + is_used: + type: boolean + example: "true" + used_by_clusters: + type: string + x-nullable: true + example: "mds-prod, drm-prod" + + Response.SecretInfoList: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/Response.SecretInfo' + meta: + type: object + $ref: '#/definitions/Meta.Pagination' + + Response.Operation: + type: object + properties: + id: + type: integer + example: 1 + cluster_name: + type: string + example: "drm-prod-cluster" + started: + type: string + format: date-time + example: "16.10.2023T11:20:00Z" + finished: + type: string + format: date-time + example: "16.10.2023T11:20:00Z" + x-nullable: true + type: + type: string + example: "deploy" + status: + type: string + example: "success" + environment: + type: string + example: "production" + + Request.Environment: + type: object + properties: + name: + type: string + example: "production" + description: + type: string + example: "environment for production" + + + Response.OperationsList: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/Response.Operation' + meta: + type: object + $ref: '#/definitions/Meta.Pagination' + + Response.Environment: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "production" + description: + type: string + x-nullable: true + example: "environment for production" + created_at: + type: string + format: date-time + example: "16.10.2023T11:20:00Z" + updated_at: + type: string + format: date-time + x-nullable: true + example: "16.10.2023T11:20:00Z" + + Response.EnvironmentsList: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/Response.Environment' + meta: + type: object + $ref: '#/definitions/Meta.Pagination' + + Response.PostgresVersions: + type: object + properties: + data: + type: array + items: + $ref: '#/definitions/Response.PostgresVersion' + + Response.PostgresVersion: + type: object + properties: + major_version: + type: integer + example: 10 + release_date: + type: string + format: date + example: "2017-10-05" + end_of_life: + type: string + format: date + example: "2022-11-10" + + Request.CreateSetting: + type: object + description: "Create new setting" + properties: + name: + type: string + value: + type: object + + Request.ChangeSetting: + type: object + description: "Change setting" + properties: + value: + type: object + x-nullable: true + + Response.Setting: + type: object + description: "Setting" + properties: + id: + type: integer + name: + type: string + value: + type: object + created_at: + type: string + format: datetime + updated_at: + type: string + format: datetime + x-nullable: true + + Response.Settings: + type: object + description: "List of settings" + properties: + data: + type: array + items: + $ref: '#/definitions/Response.Setting' + mete: + type: object + $ref: '#/definitions/Meta.Pagination' \ No newline at end of file diff --git a/console/service/db/README.md b/console/service/db/README.md new file mode 100644 index 000000000..aa7edf00d --- /dev/null +++ b/console/service/db/README.md @@ -0,0 +1,107 @@ +## Database Schema for PostgreSQL Cluster Console + +### Introduction + +This project uses [Goose](https://github.com/pressly/goose) for versioning and managing database schema changes. Goose is a database migration tool that enables database version control, much like Git does for source code. It allows defining and tracking changes in the database schema over time, ensuring consistency and reproducibility. The backend service is responsible for applying migrations. + +For more information on using Goose, see the [Goose documentation](https://github.com/pressly/goose). + +### Database Migrations +Database migrations are SQL scripts that modify the schema of the database. Each migration script should be placed in the `console/db/migrations` directory and follow Goose's naming convention to ensure they are applied in the correct order. + +**Naming Convention** +Goose uses a specific naming convention to order and apply migrations: + +- Versioned Migrations: These migrations have a version number and are applied in sequence. The naming format is `_.sql` + - Example: `20240520144338_initial_scheme_setup` + - Note: You can use the following command `goose create mogration_file_name sql` to create a new migration file. + +Example migrations: +```shell +goose -dir ./console/db/migrations postgres \ +"host= port=5432 user=postgres password= dbname=" \ +up +``` + +### Validating Migrations + +To check the status of migrations, run: +```shell +goose -dir ./console/db/migrations postgres \ +"host= port=5432 user=postgres password= dbname=" \ +status +``` + +Output example: +``` +status + +2024/05/20 17:50:33 Applied At Migration +2024/05/20 17:50:33 ======================================= +2024/05/20 17:50:33 Mon May 20 14:49:26 2024 -- 20240520144338_2.0.0_initial_scheme_setup.sql +``` + +### Database Schema + +#### Tables: +- `cloud_providers` + - Table containing cloud providers information +- `cloud_regions` + - Table containing cloud regions information for various cloud providers +- `cloud_instances` + - Table containing cloud instances information (including the approximate price) for various cloud providers +- `cloud_volumes` + - Table containing cloud volume information (including the approximate price) for various cloud providers +- `cloud_images` + - Table containing cloud images information for various cloud providers + - Note: For all cloud providers except AWS, the image is the same for all regions. For AWS, the image must be specified for each specific region. +- `secrets` + - Table containing secrets for accessing cloud providers and servers + - Note: The data is encrypted using the pgcrypto extension and a symmetric key. This symmetric key is generated at the application level and is unique for each installation. +- `projects` + - Table containing information about projects + - Default: 'default' +- `environments` + - Table containing information about environments + - Default: 'production', 'staging', 'test', 'dev', 'benchmarking' +- `clusters` + - Table containing information about Postgres clusters +- `servers` + - Table containing information about servers within a Postgres cluster +- `extensions` + - The table stores information about Postgres extensions, including name, description, supported Postgres version range, and whether the extension is a contrib module or third-party. + - 'postgres_min_version' and 'postgres_max_version' define the range of Postgres versions supported by extensions. If the postgres_max_version is NULL, it is assumed that the extension is still supported by new versions of Postgres. +- `operations` + - Table containing logs of operations performed on cluster. + - Note: The migration includes a DO block that checks for the presence of the timescaledb extension. If the extension is installed, the operations table is converted into a hypertable with monthly partitioning. Additionally, the block checks the timescaledb license. If the license is a Community license (timescale), a hypertable compression policy is created for partitions older than one month. + +#### Views: +- `v_secrets_list` + - Displays a list of secrets (without revealing secret values) along with additional metadata such as creation and update timestamps. It also includes information about whether each secret is in use and, if so, provides details on which clusters and servers are utilizing the secret. +- `v_operations` + - Displays a list of operations, with additional columns such as the name of the cluster and environment. + +#### Functions: +- `update_server_count` + - Function to update the server_count column in the clusters table. + - Note: This function calculates the number of servers associated with a specific cluster and updates the server_count accordingly. The trigger `update_server_count_trigger` is automatically executed whenever there are INSERT, UPDATE, or DELETE operations on the servers table. This ensures that the server_count in the clusters table is always accurate and up-to-date. +- `add_secret` + - Function to add a secret. + - Usage examples (project_id, secret_type, secret_name, secret_value, encryption_key): + - `SELECT add_secret(1, 'ssh_key', '', '{"private_key": ""}', 'my_encryption_key');` + - `SELECT add_secret(1, 'password', '', '{"username": "", "password": ""}', 'my_encryption_key');` + - `SELECT add_secret(1, 'cloud_secret', '', '{"AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": ""}', 'my_encryption_key');` +- `update_secret` + - Function to update a secret. + - Usage example: + - `SELECT update_secret(, '', '', '', '');` +- `get_secret` + - Function to get a secret value in JSON format. + - Usage example (secret_id, encryption_key): + - `SELECT get_secret(1, 'my_encryption_key');` +- `get_extensions` + - Function to get a list of available extensions in JSON format. All or 'contrib'/'third_party' only (optional). + - Usage examples: + - `SELECT get_extensions(16);` + - `SELECT get_extensions(16, 'contrib');` + - `SELECT get_extensions(16, 'third_party');` diff --git a/console/service/db/migrations/20240520144338_2.0.0_initial_scheme_setup.sql b/console/service/db/migrations/20240520144338_2.0.0_initial_scheme_setup.sql new file mode 100644 index 000000000..2cd284de8 --- /dev/null +++ b/console/service/db/migrations/20240520144338_2.0.0_initial_scheme_setup.sql @@ -0,0 +1,1035 @@ +-- +goose Up + +-- Create extensions +CREATE SCHEMA IF NOT EXISTS extensions; +CREATE EXTENSION IF NOT EXISTS moddatetime SCHEMA extensions; +CREATE EXTENSION IF NOT EXISTS pgcrypto SCHEMA extensions; + +-- cloud_providers +CREATE TABLE public.cloud_providers ( + provider_name text NOT NULL, + provider_description text NOT NULL, + provider_image text +); + +COMMENT ON TABLE public.cloud_providers IS 'Table containing cloud providers information'; +COMMENT ON COLUMN public.cloud_providers.provider_name IS 'The name of the cloud provider'; +COMMENT ON COLUMN public.cloud_providers.provider_description IS 'A description of the cloud provider'; + +INSERT INTO public.cloud_providers (provider_name, provider_description, provider_image) VALUES + ('aws', 'Amazon Web Services', 'aws.png'), + ('gcp', 'Google Cloud Platform', 'gcp.png'), + ('azure', 'Microsoft Azure', 'azure.png'), + ('digitalocean', 'DigitalOcean', 'digitalocean.png'), + ('hetzner', 'Hetzner Cloud', 'hetzner.png'); + +ALTER TABLE ONLY public.cloud_providers + ADD CONSTRAINT cloud_providers_pkey PRIMARY KEY (provider_name); + + +-- cloud_regions +CREATE TABLE public.cloud_regions ( + cloud_provider text NOT NULL, + region_group text NOT NULL, + region_name text NOT NULL, + region_description text NOT NULL +); + +COMMENT ON TABLE public.cloud_regions IS 'Table containing cloud regions information for various cloud providers'; +COMMENT ON COLUMN public.cloud_regions.cloud_provider IS 'The name of the cloud provider'; +COMMENT ON COLUMN public.cloud_regions.region_group IS 'The geographical group of the cloud region'; +COMMENT ON COLUMN public.cloud_regions.region_name IS 'The specific name of the cloud region'; +COMMENT ON COLUMN public.cloud_regions.region_description IS 'A description of the cloud region'; + +INSERT INTO public.cloud_regions (cloud_provider, region_group, region_name, region_description) VALUES + ('aws', 'Africa', 'af-south-1', 'Africa (Cape Town)'), + ('aws', 'Asia Pacific', 'ap-east-1', 'Asia Pacific (Hong Kong)'), + ('aws', 'Asia Pacific', 'ap-south-1', 'Asia Pacific (Mumbai)'), + ('aws', 'Asia Pacific', 'ap-south-2', 'Asia Pacific (Hyderabad)'), + ('aws', 'Asia Pacific', 'ap-southeast-3', 'Asia Pacific (Jakarta)'), + ('aws', 'Asia Pacific', 'ap-southeast-4', 'Asia Pacific (Melbourne)'), + ('aws', 'Asia Pacific', 'ap-northeast-1', 'Asia Pacific (Tokyo)'), + ('aws', 'Asia Pacific', 'ap-northeast-2', 'Asia Pacific (Seoul)'), + ('aws', 'Asia Pacific', 'ap-northeast-3', 'Asia Pacific (Osaka)'), + ('aws', 'Asia Pacific', 'ap-southeast-1', 'Asia Pacific (Singapore)'), + ('aws', 'Asia Pacific', 'ap-southeast-2', 'Asia Pacific (Sydney)'), + ('aws', 'Europe', 'eu-central-1', 'Europe (Frankfurt)'), + ('aws', 'Europe', 'eu-west-1', 'Europe (Ireland)'), + ('aws', 'Europe', 'eu-west-2', 'Europe (London)'), + ('aws', 'Europe', 'eu-west-3', 'Europe (Paris)'), + ('aws', 'Europe', 'eu-north-1', 'Europe (Stockholm)'), + ('aws', 'Europe', 'eu-south-1', 'Europe (Milan)'), + ('aws', 'Europe', 'eu-south-2', 'Europe (Spain)'), + ('aws', 'Europe', 'eu-central-2', 'Europe (Zurich)'), + ('aws', 'Middle East', 'me-south-1', 'Middle East (Bahrain)'), + ('aws', 'Middle East', 'me-central-1', 'Middle East (UAE)'), + ('aws', 'North America', 'us-east-1', 'US East (N. Virginia)'), + ('aws', 'North America', 'us-east-2', 'US East (Ohio)'), + ('aws', 'North America', 'us-west-1', 'US West (N. California)'), + ('aws', 'North America', 'us-west-2', 'US West (Oregon)'), + ('aws', 'North America', 'ca-central-1', 'Canada (Central)'), + ('aws', 'North America', 'ca-west-1', 'Canada (Calgary)'), + ('aws', 'South America', 'sa-east-1', 'South America (São Paulo)'), + ('gcp', 'Africa', 'africa-south1', 'Johannesburg'), + ('gcp', 'Asia Pacific', 'asia-east1', 'Taiwan'), + ('gcp', 'Asia Pacific', 'asia-east2', 'Hong Kong'), + ('gcp', 'Asia Pacific', 'asia-northeast1', 'Tokyo'), + ('gcp', 'Asia Pacific', 'asia-northeast2', 'Osaka'), + ('gcp', 'Asia Pacific', 'asia-northeast3', 'Seoul'), + ('gcp', 'Asia Pacific', 'asia-south1', 'Mumbai'), + ('gcp', 'Asia Pacific', 'asia-south2', 'Delhi'), + ('gcp', 'Asia Pacific', 'asia-southeast1', 'Singapore'), + ('gcp', 'Asia Pacific', 'asia-southeast2', 'Jakarta'), + ('gcp', 'Australia', 'australia-southeast1', 'Sydney'), + ('gcp', 'Australia', 'australia-southeast2', 'Melbourne'), + ('gcp', 'Europe', 'europe-central2', 'Warsaw'), + ('gcp', 'Europe', 'europe-north1', 'Finland'), + ('gcp', 'Europe', 'europe-southwest1', 'Madrid'), + ('gcp', 'Europe', 'europe-west1', 'Belgium'), + ('gcp', 'Europe', 'europe-west10', 'Berlin'), + ('gcp', 'Europe', 'europe-west12', 'Turin'), + ('gcp', 'Europe', 'europe-west2', 'London'), + ('gcp', 'Europe', 'europe-west3', 'Frankfurt'), + ('gcp', 'Europe', 'europe-west4', 'Netherlands'), + ('gcp', 'Europe', 'europe-west6', 'Zurich'), + ('gcp', 'Europe', 'europe-west8', 'Milan'), + ('gcp', 'Europe', 'europe-west9', 'Paris'), + ('gcp', 'Middle East', 'me-central1', 'Doha'), + ('gcp', 'Middle East', 'me-central2', 'Dammam'), + ('gcp', 'Middle East', 'me-west1', 'Tel Aviv'), + ('gcp', 'North America', 'northamerica-northeast1', 'Montréal'), + ('gcp', 'North America', 'northamerica-northeast2', 'Toronto'), + ('gcp', 'North America', 'us-central1', 'Iowa'), + ('gcp', 'North America', 'us-east1', 'South Carolina'), + ('gcp', 'North America', 'us-east4', 'Northern Virginia'), + ('gcp', 'North America', 'us-east5', 'Columbus'), + ('gcp', 'North America', 'us-south1', 'Dallas'), + ('gcp', 'North America', 'us-west1', 'Oregon'), + ('gcp', 'North America', 'us-west2', 'Los Angeles'), + ('gcp', 'North America', 'us-west3', 'Salt Lake City'), + ('gcp', 'North America', 'us-west4', 'Las Vegas'), + ('gcp', 'South America', 'southamerica-east1', 'São Paulo'), + ('gcp', 'South America', 'southamerica-west1', 'Santiago'), + ('azure', 'Africa', 'southafricanorth', 'South Africa North (Johannesburg)'), + ('azure', 'Africa', 'southafricawest', 'South Africa West (Cape Town)'), + ('azure', 'Asia Pacific', 'australiacentral', 'Australia Central (Canberra)'), + ('azure', 'Asia Pacific', 'australiacentral2', 'Australia Central 2 (Canberra)'), + ('azure', 'Asia Pacific', 'australiaeast', 'Australia East (New South Wales)'), + ('azure', 'Asia Pacific', 'australiasoutheast', 'Australia Southeast (Victoria)'), + ('azure', 'Asia Pacific', 'centralindia', 'Central India (Pune)'), + ('azure', 'Asia Pacific', 'eastasia', 'East Asia (Hong Kong)'), + ('azure', 'Asia Pacific', 'japaneast', 'Japan East (Tokyo, Saitama)'), + ('azure', 'Asia Pacific', 'japanwest', 'Japan West (Osaka)'), + ('azure', 'Asia Pacific', 'jioindiacentral', 'Jio India Central (Nagpur)'), + ('azure', 'Asia Pacific', 'jioindiawest', 'Jio India West (Jamnagar)'), + ('azure', 'Asia Pacific', 'koreacentral', 'Korea Central (Seoul)'), + ('azure', 'Asia Pacific', 'koreasouth', 'Korea South (Busan)'), + ('azure', 'Asia Pacific', 'southeastasia', 'Southeast Asia (Singapore)'), + ('azure', 'Asia Pacific', 'southindia', 'South India (Chennai)'), + ('azure', 'Asia Pacific', 'westindia', 'West India (Mumbai)'), + ('azure', 'Europe', 'francecentral', 'France Central (Paris)'), + ('azure', 'Europe', 'francesouth', 'France South (Marseille)'), + ('azure', 'Europe', 'germanynorth', 'Germany North (Berlin)'), + ('azure', 'Europe', 'germanywestcentral', 'Germany West Central (Frankfurt)'), + ('azure', 'Europe', 'italynorth', 'Italy North (Milan)'), + ('azure', 'Europe', 'northeurope', 'North Europe (Ireland)'), + ('azure', 'Europe', 'norwayeast', 'Norway East (Norway)'), + ('azure', 'Europe', 'norwaywest', 'Norway West (Norway)'), + ('azure', 'Europe', 'polandcentral', 'Poland Central (Warsaw)'), + ('azure', 'Europe', 'swedencentral', 'Sweden Central (Gävle)'), + ('azure', 'Europe', 'switzerlandnorth', 'Switzerland North (Zurich)'), + ('azure', 'Europe', 'switzerlandwest', 'Switzerland West (Geneva)'), + ('azure', 'Europe', 'uksouth', 'UK South (London)'), + ('azure', 'Europe', 'ukwest', 'UK West (Cardiff)'), + ('azure', 'Europe', 'westeurope', 'West Europe (Netherlands)'), + ('azure', 'Mexico', 'mexicocentral', 'Mexico Central (Querétaro State)'), + ('azure', 'Middle East', 'qatarcentral', 'Qatar Central (Doha)'), + ('azure', 'Middle East', 'uaecentral', 'UAE Central (Abu Dhabi)'), + ('azure', 'Middle East', 'uaenorth', 'UAE North (Dubai)'), + ('azure', 'South America', 'brazilsouth', 'Brazil South (Sao Paulo State)'), + ('azure', 'South America', 'brazilsoutheast', 'Brazil Southeast (Rio)'), + ('azure', 'North America', 'centralus', 'Central US (Iowa)'), + ('azure', 'North America', 'eastus', 'East US (Virginia)'), + ('azure', 'North America', 'eastus2', 'East US 2 (Virginia)'), + ('azure', 'North America', 'eastusstg', 'East US STG (Virginia)'), + ('azure', 'North America', 'northcentralus', 'North Central US (Illinois)'), + ('azure', 'North America', 'southcentralus', 'South Central US (Texas)'), + ('azure', 'North America', 'westcentralus', 'West Central US (Wyoming)'), + ('azure', 'North America', 'westus', 'West US (California)'), + ('azure', 'North America', 'westus2', 'West US 2 (Washington)'), + ('azure', 'North America', 'westus3', 'West US 3 (Phoenix)'), + ('azure', 'North America', 'canadaeast', 'Canada East (Quebec)'), + ('azure', 'North America', 'canadacentral', 'Canada Central (Toronto)'), + ('azure', 'South America', 'brazilus', 'Brazil US (South America)'), + ('digitalocean', 'Asia Pacific', 'sgp1', 'Singapore (Datacenter 1)'), + ('digitalocean', 'Asia Pacific', 'blr1', 'Bangalore (Datacenter 1)'), + ('digitalocean', 'Australia', 'syd1', 'Sydney (Datacenter 1)'), + ('digitalocean', 'Europe', 'ams3', 'Amsterdam (Datacenter 3)'), + ('digitalocean', 'Europe', 'lon1', 'London (Datacenter 1)'), + ('digitalocean', 'Europe', 'fra1', 'Frankfurt (Datacenter 1)'), + ('digitalocean', 'North America', 'nyc1', 'New York (Datacenter 1)'), + ('digitalocean', 'North America', 'nyc3', 'New York (Datacenter 3)'), + ('digitalocean', 'North America', 'sfo2', 'San Francisco (Datacenter 2)'), + ('digitalocean', 'North America', 'sfo3', 'San Francisco (Datacenter 3)'), + ('digitalocean', 'North America', 'tor1', 'Toronto (Datacenter 1)'), + ('hetzner', 'Europe', 'nbg1', 'Nuremberg'), + ('hetzner', 'Europe', 'zfsn1', 'Falkenstein'), + ('hetzner', 'Europe', 'hel1', 'Helsinki'), + ('hetzner', 'North America', 'hil', 'Hillsboro, OR'), + ('hetzner', 'North America', 'ash', 'Ashburn, VA'); + +ALTER TABLE ONLY public.cloud_regions + ADD CONSTRAINT cloud_regions_pkey PRIMARY KEY (cloud_provider, region_group, region_name); + +ALTER TABLE ONLY public.cloud_regions + ADD CONSTRAINT cloud_regions_cloud_provider_fkey FOREIGN KEY (cloud_provider) REFERENCES public.cloud_providers(provider_name); + + +-- cloud_instances +CREATE TABLE public.cloud_instances ( + cloud_provider text NOT NULL, + instance_group text NOT NULL, + instance_name text NOT NULL, + arch text DEFAULT 'amd64' NOT NULL, + cpu integer NOT NULL, + ram integer NOT NULL, + price_hourly numeric NOT NULL, + price_monthly numeric NOT NULL, + currency CHAR(1) DEFAULT '$' NOT NULL, + updated_at timestamp DEFAULT CURRENT_TIMESTAMP +); + +COMMENT ON TABLE public.cloud_instances IS 'Table containing cloud instances information for various cloud providers'; +COMMENT ON COLUMN public.cloud_instances.cloud_provider IS 'The name of the cloud provider'; +COMMENT ON COLUMN public.cloud_instances.instance_group IS 'The group of the instance size'; +COMMENT ON COLUMN public.cloud_instances.instance_name IS 'The specific name of the cloud instance'; +COMMENT ON COLUMN public.cloud_instances.arch IS 'The architecture of the instance'; +COMMENT ON COLUMN public.cloud_instances.cpu IS 'The number of CPUs of the instance'; +COMMENT ON COLUMN public.cloud_instances.ram IS 'The amount of RAM (in GB) of the instance'; +COMMENT ON COLUMN public.cloud_instances.price_hourly IS 'The hourly price of the instance'; +COMMENT ON COLUMN public.cloud_instances.price_monthly IS 'The monthly price of the instance'; +COMMENT ON COLUMN public.cloud_instances.currency IS 'The currency of the price (default: $)'; +COMMENT ON COLUMN public.cloud_instances.updated_at IS 'The date when the instance information was last updated'; + +-- The price is approximate because it is specified for one region and may differ in other regions. +-- aws, gcp, azure: the price is for the region 'US East' +INSERT INTO public.cloud_instances (cloud_provider, instance_group, instance_name, cpu, ram, price_hourly, price_monthly, currency, updated_at) VALUES + ('aws', 'Small Size', 't3.small', 2, 2, 0.021, 14.976, '$', '2024-05-15'), + ('aws', 'Small Size', 't3.medium', 2, 4, 0.042, 29.952, '$', '2024-05-15'), + ('aws', 'Small Size', 'm6i.large', 2, 8, 0.096, 69.120, '$', '2024-05-15'), + ('aws', 'Small Size', 'r6i.large', 2, 16, 0.126, 90.720, '$', '2024-05-15'), + ('aws', 'Small Size', 'm6i.xlarge', 4, 16, 0.192, 138.240, '$', '2024-05-15'), + ('aws', 'Small Size', 'r6i.xlarge', 4, 32, 0.252, 181.440, '$', '2024-05-15'), + ('aws', 'Medium Size', 'm6i.2xlarge', 8, 32, 0.384, 276.480, '$', '2024-05-15'), + ('aws', 'Medium Size', 'r6i.2xlarge', 8, 64, 0.504, 362.880, '$', '2024-05-15'), + ('aws', 'Medium Size', 'm6i.4xlarge', 16, 64, 0.768, 552.960, '$', '2024-05-15'), + ('aws', 'Medium Size', 'r6i.4xlarge', 16, 128, 1.008, 725.760, '$', '2024-05-15'), + ('aws', 'Medium Size', 'm6i.8xlarge', 32, 128, 1.536, 1105.920, '$', '2024-05-15'), + ('aws', 'Medium Size', 'r6i.8xlarge', 32, 256, 2.016, 1451.520, '$', '2024-05-15'), + ('aws', 'Medium Size', 'm6i.12xlarge', 48, 192, 2.304, 1658.880, '$', '2024-05-15'), + ('aws', 'Medium Size', 'r6i.12xlarge', 48, 384, 3.024, 2177.280, '$', '2024-05-15'), + ('aws', 'Large Size', 'm6i.16xlarge', 64, 256, 3.072, 2211.840, '$', '2024-05-15'), + ('aws', 'Large Size', 'r6i.16xlarge', 64, 512, 4.032, 2903.040, '$', '2024-05-15'), + ('aws', 'Large Size', 'm6i.24xlarge', 96, 384, 4.608, 3317.760, '$', '2024-05-15'), + ('aws', 'Large Size', 'r6i.24xlarge', 96, 768, 6.048, 4354.560, '$', '2024-05-15'), + ('aws', 'Large Size', 'm6i.32xlarge', 128, 512, 6.144, 4423.680, '$', '2024-05-15'), + ('aws', 'Large Size', 'r6i.32xlarge', 128, 1024, 8.064, 5806.080, '$', '2024-05-15'), + ('aws', 'Large Size', 'm7i.48xlarge', 192, 768, 9.677, 6967.296, '$', '2024-05-15'), + ('aws', 'Large Size', 'r7i.48xlarge', 192, 1536, 12.701, 9144.576, '$', '2024-05-15'), + ('gcp', 'Small Size', 'e2-small', 2, 2, 0.017, 12.228, '$', '2024-05-15'), + ('gcp', 'Small Size', 'e2-medium', 2, 4, 0.034, 24.457, '$', '2024-05-15'), + ('gcp', 'Small Size', 'n2-standard-2', 2, 8, 0.097, 70.896, '$', '2024-05-15'), + ('gcp', 'Small Size', 'n2-highmem-2', 2, 16, 0.131, 95.640, '$', '2024-05-15'), + ('gcp', 'Small Size', 'n2-standard-4', 4, 16, 0.194, 141.792, '$', '2024-05-15'), + ('gcp', 'Small Size', 'n2-highmem-4', 4, 32, 0.262, 191.280, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-standard-8', 8, 32, 0.388, 283.585, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-highmem-8', 8, 64, 0.524, 382.561, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-standard-16', 16, 64, 0.777, 567.169, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-highmem-16', 16, 128, 1.048, 765.122, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-standard-32', 32, 128, 1.554, 1134.338, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-highmem-32', 32, 256, 2.096, 1530.244, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-standard-48', 48, 192, 2.331, 1701.507, '$', '2024-05-15'), + ('gcp', 'Medium Size', 'n2-highmem-48', 48, 384, 3.144, 2295.365, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-standard-64', 64, 256, 3.108, 2268.676, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-highmem-64', 64, 512, 4.192, 3060.487, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-standard-80', 80, 320, 3.885, 2835.846, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-highmem-80', 80, 640, 5.241, 3825.609, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-standard-96', 96, 384, 4.662, 3403.015, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-highmem-96', 96, 768, 6.289, 4590.731, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-standard-128', 128, 512, 6.216, 4537.353, '$', '2024-05-15'), + ('gcp', 'Large Size', 'n2-highmem-128', 128, 864, 7.707, 5626.092, '$', '2024-05-15'), + ('gcp', 'Large Size', 'c3-standard-176', 176, 704, 9.188, 6706.913, '$', '2024-05-15'), + ('gcp', 'Large Size', 'c3-highmem-176', 176, 1408, 12.394, 9047.819, '$', '2024-05-15'), + ('azure', 'Small Size', 'Standard_B1ms', 1, 2, 0.021, 15.111, '$', '2024-05-15'), + ('azure', 'Small Size', 'Standard_B2s', 2, 4, 0.042, 30.368, '$', '2024-05-15'), + ('azure', 'Small Size', 'Standard_D2s_v5', 2, 8, 0.096, 70.080, '$', '2024-05-15'), + ('azure', 'Small Size', 'Standard_E2s_v5', 2, 16, 0.126, 91.980, '$', '2024-05-15'), + ('azure', 'Small Size', 'Standard_D4s_v5', 4, 16, 0.192, 140.160, '$', '2024-05-15'), + ('azure', 'Small Size', 'Standard_E4s_v5', 4, 32, 0.252, 183.960, '$', '2024-05-15'), + ('azure', 'Medium Size', 'Standard_D8s_v5', 8, 32, 0.384, 280.320, '$', '2024-05-15'), + ('azure', 'Medium Size', 'Standard_E8s_v5', 8, 64, 0.504, 367.920, '$', '2024-05-15'), + ('azure', 'Medium Size', 'Standard_D16s_v5', 16, 64, 0.768, 560.640, '$', '2024-05-15'), + ('azure', 'Medium Size', 'Standard_E16s_v5', 16, 128, 1.008, 735.840, '$', '2024-05-15'), + ('azure', 'Medium Size', 'Standard_D32s_v5', 32, 128, 1.536, 1121.280, '$', '2024-05-15'), + ('azure', 'Medium Size', 'Standard_E32s_v5', 32, 256, 2.016, 1471.680, '$', '2024-05-15'), + ('azure', 'Large Size', 'Standard_D48s_v5', 48, 192, 2.304, 1681.920, '$', '2024-05-15'), + ('azure', 'Large Size', 'Standard_E48s_v5', 48, 384, 3.024, 2207.520, '$', '2024-05-15'), + ('azure', 'Large Size', 'Standard_D64s_v5', 64, 256, 3.072, 2242.560, '$', '2024-05-15'), + ('azure', 'Large Size', 'Standard_E64s_v5', 64, 512, 4.032, 2943.360, '$', '2024-05-15'), + ('azure', 'Large Size', 'Standard_D96s_v5', 96, 384, 4.608, 3363.840, '$', '2024-05-15'), + ('azure', 'Large Size', 'Standard_E96s_v5', 96, 672, 6.048, 4415.040, '$', '2024-05-15'), + ('digitalocean', 'Small Size', 's-2vcpu-2gb', 2, 2, 0.027, 18.000, '$', '2024-05-15'), + ('digitalocean', 'Small Size', 's-2vcpu-4gb', 2, 4, 0.036, 24.000, '$', '2024-05-15'), + ('digitalocean', 'Small Size', 'g-2vcpu-8gb', 2, 8, 0.094, 63.000, '$', '2024-05-15'), + ('digitalocean', 'Small Size', 'm-2vcpu-16gb', 2, 16, 0.125, 84.000, '$', '2024-05-15'), + ('digitalocean', 'Small Size', 'g-4vcpu-16gb', 4, 16, 0.188, 126.000, '$', '2024-05-15'), + ('digitalocean', 'Small Size', 'm-4vcpu-32gb', 4, 32, 0.250, 168.000, '$', '2024-05-15'), + ('digitalocean', 'Medium Size', 'g-8vcpu-32gb', 8, 32, 0.375, 252.000, '$', '2024-05-15'), + ('digitalocean', 'Medium Size', 'm-8vcpu-64gb', 8, 64, 0.500, 336.000, '$', '2024-05-15'), + ('digitalocean', 'Medium Size', 'g-16vcpu-64gb', 16, 64, 0.750, 504.000, '$', '2024-05-15'), + ('digitalocean', 'Medium Size', 'm-16vcpu-128gb', 16, 128, 1.000, 672.000, '$', '2024-05-15'), + ('digitalocean', 'Medium Size', 'g-32vcpu-128gb', 32, 128, 1.500, 1008.000, '$', '2024-05-15'), + ('digitalocean', 'Medium Size', 'm-32vcpu-256gb', 32, 256, 2.000, 1344.000, '$', '2024-05-15'), + ('digitalocean', 'Medium Size', 'g-48vcpu-192gb', 48, 192, 2.699, 1814.000, '$', '2024-05-15'), + ('hetzner', 'Small Size', 'CPX11', 2, 2, 0.007, 5.180, '€', '2024-07-21'), + ('hetzner', 'Small Size', 'CPX21', 3, 4, 0.010, 8.980, '€', '2024-07-21'), + ('hetzner', 'Small Size', 'CCX13', 2, 8, 0.024, 14.860, '€', '2024-05-15'), + ('hetzner', 'Small Size', 'CCX23', 4, 16, 0.047, 29.140, '€', '2024-05-15'), + ('hetzner', 'Medium Size', 'CCX33', 8, 32, 0.093, 57.700, '€', '2024-05-15'), + ('hetzner', 'Medium Size', 'CCX43', 16, 64, 0.184, 114.820, '€', '2024-05-15'), + ('hetzner', 'Medium Size', 'CCX53', 32, 128, 0.367, 229.060, '€', '2024-05-15'), + ('hetzner', 'Medium Size', 'CCX63', 48, 192, 0.550, 343.300, '€', '2024-05-15'); + +ALTER TABLE ONLY public.cloud_instances + ADD CONSTRAINT cloud_instances_pkey PRIMARY KEY (cloud_provider, instance_group, instance_name); + +ALTER TABLE ONLY public.cloud_instances + ADD CONSTRAINT cloud_instances_cloud_provider_fkey FOREIGN KEY (cloud_provider) REFERENCES public.cloud_providers(provider_name); + +-- this trigger will set the "updated_at" column to the current timestamp for every update +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.cloud_instances + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + + +-- cloud_volumes +CREATE TABLE public.cloud_volumes ( + cloud_provider text NOT NULL, + volume_type text NOT NULL, + volume_description text NOT NULL, + volume_min_size integer NOT NULL, + volume_max_size integer NOT NULL, + price_monthly numeric NOT NULL, + currency CHAR(1) DEFAULT '$' NOT NULL, + is_default boolean NOT NULL DEFAULT false, + updated_at timestamp DEFAULT CURRENT_TIMESTAMP +); + +COMMENT ON TABLE public.cloud_volumes IS 'Table containing cloud volume information for various cloud providers'; +COMMENT ON COLUMN public.cloud_volumes.cloud_provider IS 'The name of the cloud provider'; +COMMENT ON COLUMN public.cloud_volumes.volume_type IS 'The type of the volume (the name provided by the API)'; +COMMENT ON COLUMN public.cloud_volumes.volume_description IS 'Description of the volume'; +COMMENT ON COLUMN public.cloud_volumes.volume_min_size IS 'The minimum size of the volume (in GB)'; +COMMENT ON COLUMN public.cloud_volumes.volume_max_size IS 'The maximum size of the volume (in GB)'; +COMMENT ON COLUMN public.cloud_volumes.price_monthly IS 'The monthly price per GB of the volume'; +COMMENT ON COLUMN public.cloud_volumes.currency IS 'The currency of the price (default: $)'; +COMMENT ON COLUMN public.cloud_volumes.is_default IS 'Indicates if the volume type is the default'; +COMMENT ON COLUMN public.cloud_volumes.updated_at IS 'The date when the volume information was last updated'; + +-- The price is approximate because it is specified for one region and may differ in other regions. +-- aws, gcp, azure: the price is for the region 'US East' +INSERT INTO public.cloud_volumes (cloud_provider, volume_type, volume_description, volume_min_size, volume_max_size, price_monthly, currency, is_default, updated_at) VALUES + ('aws', 'st1', 'Throughput Optimized HDD Disk (Max throughput: 500 MiB/s, Max IOPS: 500)', 125, 16000, 0.045, '$', false, '2024-05-15'), + ('aws', 'gp3', 'General Purpose SSD Disk (Max throughput: 1,000 MiB/s, Max IOPS: 16,000)', 10, 16000, 0.080, '$', true, '2024-05-15'), + ('aws', 'io2', 'Provisioned IOPS SSD Disk (Max throughput: 4,000 MiB/s, Max IOPS: 256,000)', 10, 64000, 0.125, '$', false, '2024-05-15'), + ('gcp', 'pd-standard', 'Standard Persistent HDD Disk (Max throughput: 180 MiB/s, Max IOPS: 3,000)', 10, 64000, 0.040, '$', false, '2024-05-15'), + ('gcp', 'pd-balanced', 'Balanced Persistent SSD Disk (Max throughput: 240 MiB/s, Max IOPS: 15,000)', 10, 64000, 0.100, '$', false, '2024-05-15'), + ('gcp', 'pd-ssd', 'SSD Persistent Disk (Max throughput: 1,200 MiB/s, Max IOPS: 100,000)', 10, 64000, 0.170, '$', true, '2024-05-15'), + ('gcp', 'pd-extreme', 'Extreme Persistent SSD Disk (Max throughput: 2,400 MiB/s, Max IOPS: 120,000)', 500, 64000, 0.125, '$', false, '2024-05-15'), + ('azure', 'Standard_LRS', 'Standard HDD (Max throughput: 500 MiB/s, Max IOPS: 2,000)', 10, 32000, 0.040, '$', false, '2024-05-15'), + ('azure', 'StandardSSD_LRS', 'Standard SSD (Max throughput: 750 MiB/s, Max IOPS: 6,000)', 10, 32000, 0.075, '$', true, '2024-05-15'), + ('azure', 'Premium_LRS', 'Premium SSD (Max throughput: 900 MiB/s, Max IOPS: 20,000)', 10, 32000, 0.132, '$', false, '2024-05-15'), + ('azure', 'UltraSSD_LRS', 'Ultra SSD (Max throughput: 10,000 MiB/s, Max IOPS: 400,000)', 10, 64000, 0.120, '$', false, '2024-05-15'), + ('digitalocean', 'ssd', 'SSD Block Storage (Max throughput: 300 MiB/s, Max IOPS: 7,500)', 10, 16000, 0.100, '$', true, '2024-05-15'), + ('hetzner', 'ssd', 'SSD Block Storage (Max throughput: N/A MiB/s, Max IOPS: N/A)', 10, 10000, 0.052, '€', true, '2024-05-15'); + +ALTER TABLE ONLY public.cloud_volumes + ADD CONSTRAINT cloud_volumes_pkey PRIMARY KEY (cloud_provider, volume_type); + +ALTER TABLE ONLY public.cloud_volumes + ADD CONSTRAINT cloud_volumes_cloud_provider_fkey FOREIGN KEY (cloud_provider) REFERENCES public.cloud_providers(provider_name); + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.cloud_volumes + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + + +-- cloud_images +CREATE TABLE public.cloud_images ( + cloud_provider text NOT NULL, + region text NOT NULL, + image jsonb NOT NULL, + arch text DEFAULT 'amd64' NOT NULL, + os_name text NOT NULL, + os_version text NOT NULL, + updated_at timestamp DEFAULT CURRENT_TIMESTAMP +); + +COMMENT ON TABLE public.cloud_images IS 'Table containing cloud images information for various cloud providers'; +COMMENT ON COLUMN public.cloud_images.cloud_provider IS 'The name of the cloud provider'; +COMMENT ON COLUMN public.cloud_images.region IS 'The region where the image is available'; +COMMENT ON COLUMN public.cloud_images.image IS 'The image details in JSON format {"variable_name": "value"}'; +COMMENT ON COLUMN public.cloud_images.arch IS 'The architecture of the operating system (default: amd64)'; +COMMENT ON COLUMN public.cloud_images.os_name IS 'The name of the operating system'; +COMMENT ON COLUMN public.cloud_images.os_version IS 'The version of the operating system'; +COMMENT ON COLUMN public.cloud_images.updated_at IS 'The date when the image information was last updated'; + +-- For all cloud providers except AWS, the image is the same for all regions. +-- For AWS, the image must be specified for each specific region. +-- The value of the "image" column is set in the format: '{"variable_name": "value"}' +-- This format provides flexibility to specify different variables for different cloud providers. +-- For example, Azure requires four variables instead of a single "server_image": +-- azure_vm_image_offer, azure_vm_image_publisher, azure_vm_image_sku, azure_vm_image_version. +INSERT INTO public.cloud_images (cloud_provider, region, image, arch, os_name, os_version, updated_at) VALUES + ('aws', 'af-south-1', '{"server_image": "ami-078b3985bbc361448"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-east-1', '{"server_image": "ami-09527147898b28c8f"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-south-1', '{"server_image": "ami-0d82b4dd52aa37cc3"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-south-2', '{"server_image": "ami-0abf88d7671119127"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-southeast-3', '{"server_image": "ami-0fb840c267c9798a4"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-southeast-4', '{"server_image": "ami-0de41b55a37aa24b6"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-northeast-1', '{"server_image": "ami-047b270f7afae25a9"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-northeast-2', '{"server_image": "ami-00dade17b7cbec931"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-northeast-3', '{"server_image": "ami-06d946d6e0d7e0a3b"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-southeast-1', '{"server_image": "ami-0b1d56f717447bdcf"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ap-southeast-2', '{"server_image": "ami-015c2e3917f29eec9"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-central-1', '{"server_image": "ami-0c027353d00750a02"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-central-2', '{"server_image": "ami-0e058ee110e570f72"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-west-1', '{"server_image": "ami-003c6328b40ce2af6"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-west-2', '{"server_image": "ami-0d05d6fe284781e13"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-west-3', '{"server_image": "ami-061fc0c4ca50c3135"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-north-1', '{"server_image": "ami-0c0a1c5b612d238ae"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-south-1', '{"server_image": "ami-02b44d454d6fdf306"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'eu-south-2', '{"server_image": "ami-0d4fc4ae17783f6bc"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'me-south-1', '{"server_image": "ami-01a4b9ac29969a669"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'me-central-1', '{"server_image": "ami-0934b7ea698655531"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'us-east-1', '{"server_image": "ami-012485deee5681dc0"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'us-east-2', '{"server_image": "ami-0df0b6b7f8f5ea0d0"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'us-west-1', '{"server_image": "ami-0344e2943d3053eda"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'us-west-2', '{"server_image": "ami-0526a31610d9ba25a"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ca-central-1', '{"server_image": "ami-0bb0ed6088d3b1bec"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'ca-west-1', '{"server_image": "ami-084542ffdec042eef"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('aws', 'sa-east-1', '{"server_image": "ami-08df96b48d7147886"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('gcp', 'all', '{"server_image": "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('azure', 'all', '{"azure_vm_image_offer": "0001-com-ubuntu-server-jammy", "azure_vm_image_publisher": "Canonical", "azure_vm_image_sku": "22_04-lts-gen2", "azure_vm_image_version": "latest"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('digitalocean', 'all', '{"server_image": "ubuntu-22-04-x64"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'), + ('hetzner', 'all', '{"server_image": "ubuntu-22.04"}', 'amd64', 'Ubuntu', '22.04 LTS', '2024-05-15'); + +ALTER TABLE ONLY public.cloud_images + ADD CONSTRAINT cloud_images_pkey PRIMARY KEY (cloud_provider, image); + +ALTER TABLE ONLY public.cloud_images + ADD CONSTRAINT cloud_images_cloud_provider_fkey FOREIGN KEY (cloud_provider) REFERENCES public.cloud_providers(provider_name); + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.cloud_images + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + + +-- Projects +CREATE TABLE public.projects ( + project_id bigserial PRIMARY KEY, + project_name varchar(50) NOT NULL UNIQUE, + project_description varchar(150), + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp +); + +COMMENT ON TABLE public.projects IS 'Table containing information about projects'; +COMMENT ON COLUMN public.projects.project_name IS 'The name of the project'; +COMMENT ON COLUMN public.projects.project_description IS 'A description of the project'; +COMMENT ON COLUMN public.projects.created_at IS 'The timestamp when the project was created'; +COMMENT ON COLUMN public.projects.updated_at IS 'The timestamp when the project was last updated'; + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.projects + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + +INSERT INTO public.projects (project_name) VALUES ('default'); + + +-- Environments +CREATE TABLE public.environments ( + environment_id bigserial PRIMARY KEY, + environment_name varchar(20) NOT NULL, + environment_description text, + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp +); + +COMMENT ON TABLE public.environments IS 'Table containing information about environments'; +COMMENT ON COLUMN public.environments.environment_name IS 'The name of the environment'; +COMMENT ON COLUMN public.environments.environment_description IS 'A description of the environment'; +COMMENT ON COLUMN public.environments.created_at IS 'The timestamp when the environment was created'; +COMMENT ON COLUMN public.environments.updated_at IS 'The timestamp when the environment was last updated'; + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.environments + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + +CREATE INDEX environments_name_idx ON public.environments (environment_name); + +INSERT INTO public.environments (environment_name) VALUES ('production'); +INSERT INTO public.environments (environment_name) VALUES ('staging'); +INSERT INTO public.environments (environment_name) VALUES ('test'); +INSERT INTO public.environments (environment_name) VALUES ('dev'); +INSERT INTO public.environments (environment_name) VALUES ('benchmarking'); + + +-- Secrets +CREATE TABLE public.secrets ( + secret_id bigserial PRIMARY KEY, + project_id bigint REFERENCES public.projects(project_id), + secret_type text NOT NULL, + secret_name text NOT NULL UNIQUE, + secret_value bytea NOT NULL, -- Encrypted data + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp +); + +COMMENT ON TABLE public.secrets IS 'Table containing secrets for accessing cloud providers and servers'; +COMMENT ON COLUMN public.secrets.project_id IS 'The ID of the project to which the secret belongs'; +COMMENT ON COLUMN public.secrets.secret_type IS 'The type of the secret (e.g., cloud_secret, ssh_key, password)'; +COMMENT ON COLUMN public.secrets.secret_name IS 'The name of the secret'; +COMMENT ON COLUMN public.secrets.secret_value IS 'The encrypted value of the secret'; +COMMENT ON COLUMN public.secrets.created_at IS 'The timestamp when the secret was created'; +COMMENT ON COLUMN public.secrets.updated_at IS 'The timestamp when the secret was last updated'; + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.secrets + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + +CREATE INDEX secrets_type_name_idx ON public.secrets (secret_type, secret_name); +CREATE INDEX secrets_id_project_idx ON public.secrets (secret_id, project_id); +CREATE INDEX secrets_project_idx ON public.secrets (project_id); + +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION add_secret(p_project_id bigint, p_secret_type text, p_secret_name text, p_secret_value json, p_encryption_key text) +RETURNS bigint AS $$ +DECLARE + v_inserted_secret_id bigint; +BEGIN + INSERT INTO public.secrets (project_id, secret_type, secret_name, secret_value) + VALUES (p_project_id, p_secret_type, p_secret_name, extensions.pgp_sym_encrypt(p_secret_value::text, p_encryption_key, 'cipher-algo=aes256')) + RETURNING secret_id INTO v_inserted_secret_id; + + RETURN v_inserted_secret_id; +END; +$$ LANGUAGE plpgsql; +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION update_secret( + p_secret_id bigint, + p_secret_type text DEFAULT NULL, + p_secret_name text DEFAULT NULL, + p_secret_value json DEFAULT NULL, + p_encryption_key text DEFAULT NULL +) +RETURNS TABLE ( + project_id bigint, + secret_id bigint, + secret_type text, + secret_name text, + created_at timestamp, + updated_at timestamp, + used boolean, + used_by_clusters text, + used_by_servers text +) AS $$ +BEGIN + IF p_secret_value IS NOT NULL AND p_encryption_key IS NULL THEN + RAISE EXCEPTION 'Encryption key must be provided when updating secret value'; + END IF; + + UPDATE public.secrets + SET + secret_name = COALESCE(p_secret_name, public.secrets.secret_name), + secret_type = COALESCE(p_secret_type, public.secrets.secret_type), + secret_value = CASE + WHEN p_secret_value IS NOT NULL THEN extensions.pgp_sym_encrypt(p_secret_value::text, p_encryption_key, 'cipher-algo=aes256') + ELSE public.secrets.secret_value + END + WHERE public.secrets.secret_id = p_secret_id; + + RETURN QUERY + SELECT + s.project_id, + s.secret_id, + s.secret_type, + s.secret_name, + s.created_at, + s.updated_at, + s.used, + s.used_by_clusters, + s.used_by_servers + FROM + public.v_secrets_list s + WHERE s.secret_id = p_secret_id; +END; +$$ LANGUAGE plpgsql; +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION get_secret(p_secret_id bigint, p_encryption_key text) +RETURNS json AS $$ +DECLARE + decrypted_value json; +BEGIN + SELECT extensions.pgp_sym_decrypt(secret_value, p_encryption_key)::json + INTO decrypted_value + FROM public.secrets + WHERE secret_id = p_secret_id; + + RETURN decrypted_value; +END; +$$ LANGUAGE plpgsql; +-- +goose StatementEnd + +-- An example of using a function to insert a secret (value in JSON format) +-- SELECT add_secret(, 'ssh_key', '', '{"private_key": ""}', ''); +-- SELECT add_secret(, 'password', '', '{"username": "", "password": ""}', ''); +-- SELECT add_secret(, 'aws', '', '{"AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": ""}', ''); + +-- An example of using the function to update a secret +-- SELECT update_secret(, '', '', '', ''); + +-- An example of using a function to get a secret +-- SELECT get_secret(, ''); + + +-- Clusters +CREATE TABLE public.clusters ( + cluster_id bigserial PRIMARY KEY, + project_id bigint REFERENCES public.projects(project_id), + environment_id bigint REFERENCES public.environments(environment_id), + secret_id bigint REFERENCES public.secrets(secret_id), + cluster_name text NOT NULL UNIQUE, + cluster_status text DEFAULT 'deploying', + cluster_description text, + cluster_location text, + connection_info jsonb, + extra_vars jsonb, + inventory jsonb, + server_count integer DEFAULT 0, + postgres_version integer, + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp, + deleted_at timestamp, + flags integer DEFAULT 0 +); + +COMMENT ON TABLE public.clusters IS 'Table containing information about Postgres clusters'; +COMMENT ON COLUMN public.clusters.project_id IS 'The ID of the project to which the cluster belongs'; +COMMENT ON COLUMN public.clusters.environment_id IS 'The environment in which the cluster is deployed (e.g., production, development, etc)'; +COMMENT ON COLUMN public.clusters.cluster_name IS 'The name of the cluster (it must be unique)'; +COMMENT ON COLUMN public.clusters.cluster_status IS 'The status of the cluster (e.q., deploying, failed, healthy, unhealthy, degraded)'; +COMMENT ON COLUMN public.clusters.cluster_description IS 'A description of the cluster (optional)'; +COMMENT ON COLUMN public.clusters.connection_info IS 'The cluster connection info'; +COMMENT ON COLUMN public.clusters.extra_vars IS 'Extra variables for Ansible specific to this cluster'; +COMMENT ON COLUMN public.clusters.inventory IS 'The Ansible inventory for this cluster'; +COMMENT ON COLUMN public.clusters.cluster_location IS 'The region/datacenter where the cluster is located'; +COMMENT ON COLUMN public.clusters.server_count IS 'The number of servers associated with the cluster'; +COMMENT ON COLUMN public.clusters.postgres_version IS 'The Postgres major version'; +COMMENT ON COLUMN public.clusters.secret_id IS 'The ID of the secret for accessing the cloud provider'; +COMMENT ON COLUMN public.clusters.created_at IS 'The timestamp when the cluster was created'; +COMMENT ON COLUMN public.clusters.updated_at IS 'The timestamp when the cluster was last updated'; +COMMENT ON COLUMN public.clusters.deleted_at IS 'The timestamp when the cluster was (soft) deleted'; +COMMENT ON COLUMN public.clusters.flags IS 'Bitmask field for storing various status flags related to the cluster'; + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.clusters + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + +CREATE INDEX clusters_id_project_id_idx ON public.clusters (cluster_id, project_id); +CREATE INDEX clusters_project_idx ON public.clusters (project_id); +CREATE INDEX clusters_environment_idx ON public.clusters (environment_id); +CREATE INDEX clusters_name_idx ON public.clusters (cluster_name); +CREATE INDEX clusters_secret_id_idx ON public.clusters (secret_id); + +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION get_cluster_name() RETURNS text AS $$ +DECLARE + new_name text; + counter int := 1; +BEGIN + LOOP + new_name := 'postgres-cluster-' || to_char(counter, 'FM00'); + -- Check if such a cluster name already exists + IF NOT EXISTS (SELECT 1 FROM public.clusters WHERE cluster_name = new_name) THEN + RETURN new_name; + END IF; + counter := counter + 1; + END LOOP; +END; +$$ LANGUAGE plpgsql; +-- +goose StatementEnd + +-- Servers +CREATE TABLE public.servers ( + server_id bigserial PRIMARY KEY, + cluster_id bigint REFERENCES public.clusters(cluster_id), + server_name text NOT NULL, + server_location text, + server_role text DEFAULT 'N/A', + server_status text DEFAULT 'N/A', + ip_address inet NOT NULL, + timeline bigint, + lag bigint, + tags jsonb, + pending_restart boolean DEFAULT false, + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp +); + +COMMENT ON TABLE public.servers IS 'Table containing information about servers within a Postgres cluster'; +COMMENT ON COLUMN public.servers.cluster_id IS 'The ID of the cluster to which the server belongs'; +COMMENT ON COLUMN public.servers.server_name IS 'The name of the server'; +COMMENT ON COLUMN public.servers.server_location IS 'The region/datacenter where the server is located'; +COMMENT ON COLUMN public.servers.server_role IS 'The role of the server (e.g., primary, replica)'; +COMMENT ON COLUMN public.servers.server_status IS 'The current status of the server'; +COMMENT ON COLUMN public.servers.ip_address IS 'The IP address of the server'; +COMMENT ON COLUMN public.servers.timeline IS 'The timeline of the Postgres'; +COMMENT ON COLUMN public.servers.lag IS 'The lag in MB of the Postgres'; +COMMENT ON COLUMN public.servers.tags IS 'The tags associated with the server'; +COMMENT ON COLUMN public.servers.pending_restart IS 'Indicates whether a restart is pending for the Postgres'; +COMMENT ON COLUMN public.servers.created_at IS 'The timestamp when the server was created'; +COMMENT ON COLUMN public.servers.updated_at IS 'The timestamp when the server was last updated'; + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.servers + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + +CREATE UNIQUE INDEX servers_cluster_id_ip_address_idx ON public.servers (cluster_id, ip_address); + +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION update_server_count() +RETURNS TRIGGER AS $$ +BEGIN + UPDATE public.clusters + SET server_count = ( + SELECT COUNT(*) + FROM public.servers + WHERE public.servers.cluster_id = NEW.cluster_id + ) + WHERE cluster_id = NEW.cluster_id; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +-- +goose StatementEnd + +-- Trigger to update server_count on changes in servers +CREATE TRIGGER update_server_count_trigger AFTER INSERT OR UPDATE OR DELETE ON public.servers + FOR EACH ROW EXECUTE FUNCTION update_server_count(); + + +-- Secrets view +CREATE VIEW public.v_secrets_list AS +SELECT + s.project_id, + s.secret_id, + s.secret_name, + s.secret_type, + s.created_at, + s.updated_at, + CASE + WHEN COUNT(c.secret_id) > 0 THEN true + ELSE false + END AS used, + COALESCE(string_agg(DISTINCT c.cluster_name, ', '), '') AS used_by_clusters +FROM + public.secrets s +LEFT JOIN LATERAL ( + SELECT cluster_name, secret_id + FROM public.clusters + WHERE secret_id = s.secret_id AND project_id = s.project_id +) c ON true +GROUP BY + s.project_id, s.secret_id, s.secret_name, s.secret_type, s.created_at, s.updated_at; + + +-- Extensions +CREATE TABLE public.extensions ( + extension_name text PRIMARY KEY, + extension_description varchar(150) NOT NULL, + extension_url text, + extension_image text, + postgres_min_version text, + postgres_max_version text, + contrib boolean NOT NULL +); + +COMMENT ON TABLE public.extensions IS 'Table containing available extensions for different Postgres versions'; +COMMENT ON COLUMN public.extensions.extension_name IS 'The name of the extension'; +COMMENT ON COLUMN public.extensions.extension_description IS 'The description of the extension'; +COMMENT ON COLUMN public.extensions.postgres_min_version IS 'The minimum Postgres version where the extension is available'; +COMMENT ON COLUMN public.extensions.postgres_max_version IS 'The maximum Postgres version where the extension is available'; +COMMENT ON COLUMN public.extensions.contrib IS 'Indicates if the extension is a contrib module or third-party extension'; + +-- The table stores information about Postgres extensions, including name, description, supported Postgres version range, +-- and whether the extension is a contrib module or third-party. +-- postgres_min_version and postgres_max_version define the range of Postgres versions supported by extensions. +-- If the postgres_max_version is NULL, it is assumed that the extension is still supported by new versions of Postgres. + +INSERT INTO public.extensions (extension_name, extension_description, postgres_min_version, postgres_max_version, extension_url, extension_image, contrib) VALUES + ('adminpack', 'administrative functions for PostgreSQL', NULL, NULL, NULL, NULL, true), + ('amcheck', 'functions for verifying relation integrity', NULL, NULL, NULL, NULL, true), + ('autoinc', 'functions for autoincrementing fields', NULL, NULL, NULL, NULL, true), + ('bloom', 'bloom access method - signature file based index', NULL, NULL, NULL, NULL, true), + ('btree_gin', 'support for indexing common datatypes in GIN', NULL, NULL, NULL, NULL, true), + ('btree_gist', 'support for indexing common datatypes in GiST', NULL, NULL, NULL, NULL, true), + ('chkpass', 'data type for auto-encrypted passwords', NULL, '10', NULL, NULL, true), + ('citext', 'data type for case-insensitive character strings', NULL, NULL, NULL, NULL, true), + ('cube', 'data type for multidimensional cubes', NULL, NULL, NULL, NULL, true), + ('dblink', 'connect to other PostgreSQL databases from within a database', NULL, NULL, NULL, NULL, true), + ('dict_int', 'text search dictionary template for integers', NULL, NULL, NULL, NULL, true), + ('dict_xsyn', 'text search dictionary template for extended synonym processing', NULL, NULL, NULL, NULL, true), + ('earthdistance', 'calculate great-circle distances on the surface of the Earth', NULL, NULL, NULL, NULL, true), + ('file_fdw', 'foreign-data wrapper for flat file access', NULL, NULL, NULL, NULL, true), + ('fuzzystrmatch', 'determine similarities and distance between strings', NULL, NULL, NULL, NULL, true), + ('hstore', 'data type for storing sets of (key, value) pairs', NULL, NULL, NULL, NULL, true), + ('insert_username', 'functions for tracking who changed a table', NULL, NULL, NULL, NULL, true), + ('intagg', 'integer aggregator and enumerator (obsolete)', NULL, NULL, NULL, NULL, true), + ('intarray', 'functions, operators, and index support for 1-D arrays of integers', NULL, NULL, NULL, NULL, true), + ('isn', 'data types for international product numbering standards', NULL, NULL, NULL, NULL, true), + ('lo', 'Large Object maintenance', NULL, NULL, NULL, NULL, true), + ('ltree', 'data type for hierarchical tree-like structures', NULL, NULL, NULL, NULL, true), + ('moddatetime', 'functions for tracking last modification time', NULL, NULL, NULL, NULL, true), + ('old_snapshot', 'utilities in support of old_snapshot_threshold', '14', NULL, NULL, NULL, true), + ('pageinspect', 'inspect the contents of database pages at a low level', NULL, NULL, NULL, NULL, true), + ('pg_buffercache', 'examine the shared buffer cache', NULL, NULL, NULL, NULL, true), + ('pg_freespacemap', 'examine the free space map (FSM)', NULL, NULL, NULL, NULL, true), + ('pg_prewarm', 'prewarm relation data', NULL, NULL, NULL, NULL, true), + ('pg_stat_statements', 'track planning and execution statistics of all SQL statements executed', NULL, NULL, NULL, NULL, true), + ('pg_surgery', 'extension to perform surgery on a damaged relation', '14', NULL, NULL, NULL, true), + ('pg_trgm', 'text similarity measurement and index searching based on trigrams', NULL, NULL, NULL, NULL, true), + ('pg_visibility', 'examine the visibility map (VM) and page-level visibility info', NULL, NULL, NULL, NULL, true), + ('pg_walinspect', 'functions to inspect contents of PostgreSQL Write-Ahead Log', '15', NULL, NULL, NULL, true), + ('pgcrypto', 'cryptographic functions', NULL, NULL, NULL, NULL, true), + ('pgrowlocks', 'show row-level locking information', NULL, NULL, NULL, NULL, true), + ('pgstattuple', 'show tuple-level statistics', NULL, NULL, NULL, NULL, true), + ('plpgsql', 'PL/pgSQL procedural language', NULL, NULL, NULL, NULL, true), + ('postgres_fdw', 'foreign-data wrapper for remote PostgreSQL servers', NULL, NULL, NULL, NULL, true), + ('refint', 'functions for implementing referential integrity (obsolete)', NULL, NULL, NULL, NULL, true), + ('seg', 'data type for representing line segments or floating-point intervals', NULL, NULL, NULL, NULL, true), + ('sslinfo', 'information about SSL certificates', NULL, NULL, NULL, NULL, true), + ('tablefunc', 'functions that manipulate whole tables, including crosstab', NULL, NULL, NULL, NULL, true), + ('tcn', 'Triggered change notifications', NULL, NULL, NULL, NULL, true), + ('timetravel', 'functions for implementing time travel', NULL, '11', NULL, NULL, true), + ('tsm_system_rows', 'TABLESAMPLE method which accepts number of rows as a limit', NULL, NULL, NULL, NULL, true), + ('tsm_system_time', 'TABLESAMPLE method which accepts time in milliseconds as a limit', NULL, NULL, NULL, NULL, true), + ('unaccent', 'text search dictionary that removes accents', NULL, NULL, NULL, NULL, true), + ('uuid-ossp', 'generate universally unique identifiers (UUIDs)', NULL, NULL, NULL, NULL, true), + ('xml2', 'XPath querying and XSLT', NULL, NULL, NULL, NULL, true), + -- Third-Party Extensions + ('citus', 'Citus is a PostgreSQL extension that transforms Postgres into a distributed database—so you can achieve high performance at any scale', 11, 16, 'https://github.com/citusdata/citus', 'citus.png', false), + ('pgaudit', 'The PostgreSQL Audit Extension provides detailed session and/or object audit logging via the standard PostgreSQL logging facility', 10, 16, 'https://github.com/pgaudit/pgaudit', 'pgaudit.png', false), + ('pg_cron', 'Job scheduler for PostgreSQL', 10, 16, 'https://github.com/citusdata/pg_cron', 'pg_cron.png', false), + ('pg_partman', 'pg_partman is an extension to create and manage both time-based and number-based table partition sets', 10, 16, 'https://github.com/pgpartman/pg_partman', 'pg_partman.png', false), + ('pg_repack', 'Reorganize tables in PostgreSQL databases with minimal locks', 10, 16, 'https://github.com/reorg/pg_repack', 'pg_repack.png', false), + ('pg_stat_kcache', 'Gather statistics about physical disk access and CPU consumption done by backends', 10, 16, 'https://github.com/powa-team/pg_stat_kcache', NULL, false), + ('pg_wait_sampling', 'Sampling based statistics of wait events', 10, 16, 'https://github.com/postgrespro/pg_wait_sampling', NULL, false), + ('pgvector', 'Open-source vector similarity search for Postgres (vector data type and ivfflat and hnsw access methods)', 11, 16, 'https://github.com/pgvector/pgvector', 'pgvector.png', false), + ('postgis', 'PostGIS extends the capabilities of the PostgreSQL relational database by adding support for storing, indexing, and querying geospatial data', 10, 16, 'https://postgis.net', 'postgis.png', false), + ('pgrouting', 'pgRouting extends the PostGIS / PostgreSQL geospatial database to provide geospatial routing functionality', 10, 16, 'https://pgrouting.org', 'pgrouting.png', false), + ('timescaledb', 'TimescaleDB is an open-source database designed to make SQL scalable for time-series data (Community Edition)', 12, 16, 'https://github.com/timescale/timescaledb', 'timescaledb.png', false); + +-- +goose StatementBegin +CREATE OR REPLACE FUNCTION get_extensions(p_postgres_version float, p_extension_type text DEFAULT 'all') +RETURNS json AS $$ +DECLARE + extensions json; +BEGIN + SELECT json_agg(row_to_json(e)) + INTO extensions + FROM ( + SELECT e.extension_name, e.extension_description, e.extension_url, e.extension_image, e.postgres_min_version, e.postgres_max_version, e.contrib + FROM public.extensions e + WHERE (e.postgres_min_version IS NULL OR e.postgres_min_version::float <= p_postgres_version) + AND (e.postgres_max_version IS NULL OR e.postgres_max_version::float >= p_postgres_version) + AND (p_extension_type = 'all' OR (p_extension_type = 'contrib' AND e.contrib = true) OR (p_extension_type = 'third_party' AND e.contrib = false)) + ORDER BY e.contrib, e.extension_image IS NULL, e.extension_name + ) e; + + RETURN extensions; +END; +$$ LANGUAGE plpgsql; +-- +goose StatementEnd + +-- An example of using a function to get a list of available extensions (all or 'contrib'/'third_party' only) +-- SELECT get_extensions(16); +-- SELECT get_extensions(16, 'contrib'); +-- SELECT get_extensions(16, 'third_party'); + + +-- Operations +CREATE TABLE public.operations ( + id bigserial, + project_id bigint REFERENCES public.projects(project_id), + cluster_id bigint REFERENCES public.clusters(cluster_id), + docker_code varchar(80) NOT NULL, + cid uuid, + operation_type text NOT NULL, + operation_status text NOT NULL CHECK (operation_status IN ('in_progress', 'success', 'failed')), + operation_log text, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp with time zone +); + +COMMENT ON TABLE public.operations IS 'Table containing logs of operations performed on clusters'; +COMMENT ON COLUMN public.operations.id IS 'The ID of the operation from the backend'; +COMMENT ON COLUMN public.clusters.project_id IS 'The ID of the project to which the operation belongs'; +COMMENT ON COLUMN public.operations.cluster_id IS 'The ID of the cluster related to the operation'; +COMMENT ON COLUMN public.operations.docker_code IS 'The CODE of the operation related to the docker daemon'; +COMMENT ON COLUMN public.operations.cid IS 'The correlation_id related to the operation'; +COMMENT ON COLUMN public.operations.operation_type IS 'The type of operation performed (e.g., deploy, edit, update, restart, delete, etc.)'; +COMMENT ON COLUMN public.operations.operation_status IS 'The status of the operation (in_progress, success, failed)'; +COMMENT ON COLUMN public.operations.operation_log IS 'The log details of the operation'; +COMMENT ON COLUMN public.operations.created_at IS 'The timestamp when the operation was created'; +COMMENT ON COLUMN public.operations.updated_at IS 'The timestamp when the operation was last updated'; + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.operations + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + +-- add created_at as part of the primary key to be able to create a hypertable +ALTER TABLE ONLY public.operations + ADD CONSTRAINT operations_pkey PRIMARY KEY (created_at, id); + +CREATE INDEX operations_project_id_idx ON public.operations (project_id); +CREATE INDEX operations_cluster_id_idx ON public.operations (cluster_id); +CREATE INDEX operations_project_cluster_id_idx ON public.operations (project_id, cluster_id, created_at); +CREATE INDEX operations_project_cluster_id_operation_type_idx ON public.operations (project_id, cluster_id, operation_type, created_at); + +-- Check if the timescaledb extension is available and create hypertable if it is +-- +goose StatementBegin +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'timescaledb') THEN + -- Convert the operations table to a hypertable + PERFORM create_hypertable('public.operations', 'created_at', chunk_time_interval => interval '1 month'); + + -- Check if the license allows compression policy + IF current_setting('timescaledb.license', true) = 'timescale' THEN + -- Enable compression on the operations hypertable, segmenting by project_id and cluster_id + ALTER TABLE public.operations SET ( + timescaledb.compress, + timescaledb.compress_orderby = 'created_at DESC, id DESC, operation_type, operation_status', + timescaledb.compress_segmentby = 'project_id, cluster_id' + ); + -- Compressing chunks older than one month + PERFORM add_compression_policy('public.operations', interval '1 month'); + ELSE + RAISE NOTICE 'Timescaledb license does not support compression policy. Skipping compression setup.'; + END IF; + ELSE + RAISE NOTICE 'Timescaledb extension is not available. Skipping hypertable and compression setup.'; + END IF; +END +$$; +-- +goose StatementEnd + +CREATE OR REPLACE VIEW public.v_operations AS +SELECT + op.project_id, + op.cluster_id, + op.id, + op.created_at AS "started", + op.updated_at AS "finished", + op.operation_type AS "type", + op.operation_status AS "status", + cl.cluster_name AS "cluster", + env.environment_name AS "environment" +FROM + public.operations op +JOIN + public.clusters cl ON op.cluster_id = cl.cluster_id +JOIN + public.projects pr ON op.project_id = pr.project_id +JOIN + public.environments env ON cl.environment_id = env.environment_id; + + +-- Postgres versions +CREATE TABLE public.postgres_versions ( + major_version integer PRIMARY KEY, + release_date date, + end_of_life date +); + +COMMENT ON TABLE public.postgres_versions IS 'Table containing the major PostgreSQL versions supported by the postgresql_cluster'; +COMMENT ON COLUMN public.postgres_versions.major_version IS 'The major version of PostgreSQL'; +COMMENT ON COLUMN public.postgres_versions.release_date IS 'The release date of the PostgreSQL version'; +COMMENT ON COLUMN public.postgres_versions.end_of_life IS 'The end of life date for the PostgreSQL version'; + +INSERT INTO public.postgres_versions (major_version, release_date, end_of_life) VALUES + (10, '2017-10-05', '2022-11-10'), + (11, '2018-10-18', '2023-11-09'), + (12, '2019-10-03', '2024-11-14'), + (13, '2020-09-24', '2025-11-13'), + (14, '2021-09-30', '2026-11-12'), + (15, '2022-10-13', '2027-11-11'), + (16, '2023-09-14', '2028-11-09'); + + +-- Settings +CREATE TABLE public.settings ( + id bigserial PRIMARY KEY, + setting_name text NOT NULL UNIQUE, + setting_value jsonb NOT NULL, + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp +); + +COMMENT ON TABLE public.settings IS 'Table containing configuration parameters, including console and other component settings'; +COMMENT ON COLUMN public.settings.setting_name IS 'The key of the setting'; +COMMENT ON COLUMN public.settings.setting_value IS 'The value of the setting'; +COMMENT ON COLUMN public.settings.created_at IS 'The timestamp when the setting was created'; +COMMENT ON COLUMN public.settings.updated_at IS 'The timestamp when the setting was last updated'; + +CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.settings + FOR EACH ROW EXECUTE FUNCTION extensions.moddatetime (updated_at); + +CREATE INDEX settings_name_idx ON public.settings (setting_name); + + +-- +goose Down + +-- Drop triggers +DROP TRIGGER update_server_count_trigger ON public.servers; +DROP TRIGGER handle_updated_at ON public.servers; +DROP TRIGGER handle_updated_at ON public.clusters; +DROP TRIGGER handle_updated_at ON public.environments; +DROP TRIGGER handle_updated_at ON public.projects; +DROP TRIGGER handle_updated_at ON public.secrets; +DROP TRIGGER handle_updated_at ON public.cloud_images; +DROP TRIGGER handle_updated_at ON public.cloud_volumes; +DROP TRIGGER handle_updated_at ON public.cloud_instances; +DROP TRIGGER handle_updated_at ON public.operations; + +-- Drop functions +DROP FUNCTION update_server_count; +DROP FUNCTION get_extensions; +DROP FUNCTION get_secret; +DROP FUNCTION add_secret; +DROP FUNCTION get_cluster_name; + +-- Drop views +DROP VIEW public.v_operations; +DROP VIEW public.v_secrets_list; + +-- Drop tables +DROP TABLE public.postgres_versions; +DROP TABLE public.operations; +DROP TABLE public.extensions; +DROP TABLE public.servers; +DROP TABLE public.clusters; +DROP TABLE public.secrets; +DROP TABLE public.environments; +DROP TABLE public.projects; +DROP TABLE public.cloud_images; +DROP TABLE public.cloud_volumes; +DROP TABLE public.cloud_instances; +DROP TABLE public.cloud_regions; +DROP TABLE public.cloud_providers; +DROP TABLE public.settings; diff --git a/console/service/env.sh b/console/service/env.sh new file mode 100755 index 000000000..ff8041393 --- /dev/null +++ b/console/service/env.sh @@ -0,0 +1,7 @@ +export PG_CONSOLE_DB_MIGRATIONDIR='./db/migrations' +export PG_CONSOLE_LOGGER_LEVEL=TRACE +export PG_CONSOLE_DB_DBNAME=db_console +export PG_CONSOLE_DOCKER_LOGDIR='/home/nikolay.gurban/log_dir' +export PG_CONSOLE_DB_PASSWORD=postgres +export PG_CONSOLE_LOGWATCHER_RUNEVERY=10m +export PG_CONSOLE_CLUSTERWATCHER_RUNEVERY=10m \ No newline at end of file diff --git a/console/service/go.mod b/console/service/go.mod new file mode 100644 index 000000000..2e8ac36a7 --- /dev/null +++ b/console/service/go.mod @@ -0,0 +1,74 @@ +module postgesql-cluster-console + +go 1.21 + +require ( + github.com/docker/docker v26.1.2+incompatible + github.com/docker/go-connections v0.5.0 + github.com/gdex-lab/go-render v1.0.1 + github.com/go-openapi/errors v0.22.0 + github.com/go-openapi/loads v0.22.0 + github.com/go-openapi/runtime v0.28.0 + github.com/go-openapi/spec v0.21.0 + github.com/go-openapi/strfmt v0.23.0 + github.com/go-openapi/swag v0.23.0 + github.com/go-openapi/validate v0.24.0 + github.com/google/uuid v1.6.0 + github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e + github.com/jackc/pgx/v5 v5.5.5 + github.com/jessevdk/go-flags v1.5.0 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/mitchellh/mapstructure v1.5.0 + github.com/pressly/goose/v3 v3.20.0 + github.com/rs/zerolog v1.32.0 + github.com/segmentio/asm v1.2.0 + go.openly.dev/pointy v1.3.0 + golang.org/x/net v0.25.0 + golang.org/x/sync v0.7.0 + gotest.tools/v3 v3.5.1 +) + +require ( + github.com/Microsoft/go-winio v0.4.14 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sethvargo/go-retry v0.2.4 // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect + go.opentelemetry.io/otel v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/console/service/go.sum b/console/service/go.sum new file mode 100644 index 000000000..1bf431c28 --- /dev/null +++ b/console/service/go.sum @@ -0,0 +1,238 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v26.1.2+incompatible h1:UVX5ZOrrfTGZZYEP+ZDq3Xn9PdHNXaSYMFPDumMqG2k= +github.com/docker/docker v26.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gdex-lab/go-render v1.0.1 h1:xk5dn5b0vAUntzcLD57sVpw6crIjkBaVHJxDd/KN2Mc= +github.com/gdex-lab/go-render v1.0.1/go.mod h1:0Cgpq7v7yfmmvplBne9VgJl97YlpT8B9RlgcjdF+Uxc= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e h1:XmA6L9IPRdUr28a+SK/oMchGgQy159wvzXA5tJ7l+40= +github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e/go.mod h1:AFIo+02s+12CEg8Gzz9kzhCbmbq6JcKNrhHffCGA9z4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.20.0 h1:uPJdOxF/Ipj7ABVNOAMJXSxwFXZGwMGHNqjC8e61VA0= +github.com/pressly/goose/v3 v3.20.0/go.mod h1:BRfF2GcG4FTG12QfdBVy3q1yveaf4ckL9vWwEcIO3lA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= +github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.openly.dev/pointy v1.3.0 h1:keht3ObkbDNdY8PWPwB7Kcqk+MAlNStk5kXZTxukE68= +go.openly.dev/pointy v1.3.0/go.mod h1:rccSKiQDQ2QkNfSVT2KG8Budnfhf3At8IWxy/3ElYes= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= +modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= +modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/console/service/internal/configuration/config.go b/console/service/internal/configuration/config.go new file mode 100644 index 000000000..3d98c666a --- /dev/null +++ b/console/service/internal/configuration/config.go @@ -0,0 +1,77 @@ +package configuration + +import ( + "fmt" + "time" + + "github.com/kelseyhightower/envconfig" +) + +type Config struct { + Logger struct { + Level string `default:"DEBUG" desc:"Log level. Accepted values: [TRACE, DEBUG, INFO, WARN, ERROR, FATAL, PANIC]"` + } + Http struct { + Host string `default:"0.0.0.0" desc:"Accepted host for connection. '0.0.0.0' for all hosts"` + Port int `default:"8080" desc:"Listening port"` + WriteTimeout time.Duration `default:"10s" desc:"Maximum duration before timing out write of the response"` + ReadTimeout time.Duration `default:"10s" desc:"Maximum duration before timing out read of the request"` + } + Https struct { + IsUsed bool `default:"false" desc:"Flag for turn on/off https"` + Host string `default:"0.0.0.0" desc:"Accepted host for connection. '0.0.0.0' for all hosts"` + Port int `default:"8081" desc:"Listening port"` + CACert string `default:"/etc/pg_console/cacert.pem" desc:"The certificate to use for secure connections"` + ServerCert string `default:"/etc/pg_console/server-cert.pem" desc:"The certificate authority file to be used with mutual tls auth"` + ServerKey string `default:"/etc/pg_console/server-key.pem" desc:"The private key to use for secure connections"` + } + Authorization struct { + Token string `default:"auth_token" desc:"Authorization token for REST API"` + } + Db struct { + Host string `default:"localhost" desc:"Database host"` + Port uint16 `default:"5432" desc:"Database port"` + DbName string `default:"postgres" desc:"Database name"` + User string `default:"postgres" desc:"Database user name"` + Password string `default:"postgres-pass" desc:"Database user password"` + MaxConns int32 `default:"10" desc:"MaxConns is the maximum size of the pool"` + MaxConnLifeTime time.Duration `default:"60s" desc:"MaxConnLifetime is the duration since creation after which a connection will be automatically closed"` + MaxConnIdleTime time.Duration `default:"60s" desc:"MaxConnIdleTime is the duration after which an idle connection will be automatically closed by the health check"` + MigrationDir string `default:"/etc/db/migrations" desc:"Path to directory with migration scripts"` + } + EncryptionKey string `default:"super_secret" desc:"Encryption key for secret storage"` + Docker struct { + Host string `default:"unix:///var/run/docker.sock" desc:"Docker host"` + LogDir string `default:"/tmp/ansible" desc:"Directory inside docker container for ansible json log"` + Image string `default:"vitabaks/postgresql_cluster:cloud" desc:"Docker image for postgresql_cluster"` + } + LogWatcher struct { + RunEvery time.Duration `default:"1m" desc:"LogWatcher run interval"` + AnalyzePast time.Duration `default:"48h" desc:"LogWatcher gets operations to analyze which created_at > now() - AnalyzePast"` + } + ClusterWatcher struct { + RunEvery time.Duration `default:"1m" desc:"ClusterWatcher run interval"` + PoolSize int64 `default:"4" desc:"Amount of async request from ClusterWatcher"` + } +} + +const cfgPrefix = "PG_CONSOLE" + +func ReadConfig() (*Config, error) { + cfg := Config{} + + err := envconfig.Process(cfgPrefix, &cfg) + if err != nil { + return nil, fmt.Errorf("failed to parse config: %s", err.Error()) + } + + return &cfg, nil +} + +func PrintUsage() { + cfg := Config{} + err := envconfig.Usage(cfgPrefix, &cfg) + if err != nil { + fmt.Printf("failed to print envconfig usage: %s", err.Error()) + } +} diff --git a/console/service/internal/controllers/cluster/delete_cluster.go b/console/service/internal/controllers/cluster/delete_cluster.go new file mode 100644 index 000000000..69ce798ae --- /dev/null +++ b/console/service/internal/controllers/cluster/delete_cluster.go @@ -0,0 +1,27 @@ +package cluster + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/restapi/operations/cluster" +) + +type deleteClusterHandler struct { + db storage.IStorage +} + +func NewDeleteClusterHandler(db storage.IStorage) cluster.DeleteClustersIDHandler { + return &deleteClusterHandler{ + db: db, + } +} + +func (h *deleteClusterHandler) Handle(param cluster.DeleteClustersIDParams) middleware.Responder { + err := h.db.DeleteClusterSoft(param.HTTPRequest.Context(), param.ID) + if err != nil { + return cluster.NewDeleteClustersIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return cluster.NewDeleteClustersIDNoContent() +} diff --git a/console/service/internal/controllers/cluster/delete_server.go b/console/service/internal/controllers/cluster/delete_server.go new file mode 100644 index 000000000..4d966ff69 --- /dev/null +++ b/console/service/internal/controllers/cluster/delete_server.go @@ -0,0 +1,41 @@ +package cluster + +import ( + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/cluster" +) + +type deleteServerHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewDeleteServerHandler(db storage.IStorage, log zerolog.Logger) cluster.DeleteServersIDHandler { + return &deleteServerHandler{ + db: db, + log: log, + } +} + +func (h *deleteServerHandler) Handle(param cluster.DeleteServersIDParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + deletedServer, err := h.db.GetServer(param.HTTPRequest.Context(), param.ID) + if err != nil { + localLog.Warn().Err(err).Msg("failed to get server from db") + } + clusterID := int64(-1) + if deletedServer != nil { + clusterID = deletedServer.ClusterID + } + err = h.db.DeleteServer(param.HTTPRequest.Context(), param.ID) + if err != nil { + return cluster.NewDeleteServersIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return cluster.NewDeleteServersIDNoContent().WithXClusterID(clusterID) +} diff --git a/console/service/internal/controllers/cluster/get_cluster.go b/console/service/internal/controllers/cluster/get_cluster.go new file mode 100644 index 000000000..6c586902b --- /dev/null +++ b/console/service/internal/controllers/cluster/get_cluster.go @@ -0,0 +1,57 @@ +package cluster + +import ( + "context" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/cluster" +) + +type getClusterHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewGetClusterHandler(db storage.IStorage, log zerolog.Logger) cluster.GetClustersIDHandler { + return &getClusterHandler{ + db: db, + log: log, + } +} + +func (h *getClusterHandler) Handle(param cluster.GetClustersIDParams) middleware.Responder { + cl, err := h.db.GetCluster(param.HTTPRequest.Context(), param.ID) + if err != nil { + return cluster.NewGetClustersIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + resp, err := getClusterInfo(param.HTTPRequest.Context(), h.db, cl) + if err != nil { + return cluster.NewGetClustersIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return cluster.NewGetClustersIDOK().WithPayload(resp) +} + +func getClusterInfo(ctx context.Context, db storage.IStorage, cl *storage.Cluster) (*models.ClusterInfo, error) { + project, err := db.GetProject(ctx, cl.ProjectID) + if err != nil { + return nil, err + } + + environment, err := db.GetEnvironment(ctx, cl.EnvironmentID) + if err != nil { + return nil, err + } + + servers, err := db.GetClusterServers(ctx, cl.ID) + if err != nil { + return nil, err + } + + return convert.ClusterToSwagger(cl, servers, environment.Name, project.Name), nil +} diff --git a/console/service/internal/controllers/cluster/get_cluster_default_name.go b/console/service/internal/controllers/cluster/get_cluster_default_name.go new file mode 100644 index 000000000..973f143d6 --- /dev/null +++ b/console/service/internal/controllers/cluster/get_cluster_default_name.go @@ -0,0 +1,33 @@ +package cluster + +import ( + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/cluster" +) + +type getClusterDefaultNameHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewGetClusterDefaultNameHandler(db storage.IStorage, log zerolog.Logger) cluster.GetClustersDefaultNameHandler { + return &getClusterDefaultNameHandler{ + db: db, + log: log, + } +} + +func (h *getClusterDefaultNameHandler) Handle(param cluster.GetClustersDefaultNameParams) middleware.Responder { + name, err := h.db.GetDefaultClusterName(param.HTTPRequest.Context()) + if err != nil { + return cluster.NewGetClustersDefaultNameBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return cluster.NewGetClustersDefaultNameOK().WithPayload(&models.ResponseClusterDefaultName{ + Name: name, + }) +} diff --git a/console/service/internal/controllers/cluster/get_clusters.go b/console/service/internal/controllers/cluster/get_clusters.go new file mode 100644 index 000000000..fe7e131e2 --- /dev/null +++ b/console/service/internal/controllers/cluster/get_clusters.go @@ -0,0 +1,107 @@ +package cluster + +import ( + "context" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/cluster" + "time" +) + +type getClustersHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewGetClustersHandler(db storage.IStorage, log zerolog.Logger) cluster.GetClustersHandler { + return &getClustersHandler{ + db: db, + log: log, + } +} + +func (h *getClustersHandler) Handle(param cluster.GetClustersParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + + project, err := h.db.GetProject(param.HTTPRequest.Context(), param.ProjectID) + if err != nil { + return cluster.NewGetClustersBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + clusters, meta, err := h.db.GetClusters(param.HTTPRequest.Context(), &storage.GetClustersReq{ + ProjectID: param.ProjectID, + Name: param.Name, + SortBy: param.SortBy, + Status: param.Status, + Location: param.Location, + ServerCount: param.ServerCount, + PostgresVersion: param.PostgresVersion, + EnvironmentID: func() *int64 { + if param.Environment == nil { + return nil + } + environment, err := h.db.GetEnvironmentByName(param.HTTPRequest.Context(), *param.Environment) + if err != nil { + localLog.Error().Err(err).Msg("failed to get environment from db") + + return nil + } + + return &environment.ID + }(), + CreatedAtFrom: (*time.Time)(param.CreatedAtFrom), + CreatedAtTo: (*time.Time)(param.CreatedAtTo), + Limit: param.Limit, + Offset: param.Offset, + }) + if err != nil { + return cluster.NewGetClustersBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + clustersResp := models.ResponseClustersInfo{ + Data: make([]*models.ClusterInfo, 0, len(clusters)), + Meta: &models.MetaPagination{ + Count: &meta.Count, + Limit: &meta.Limit, + Offset: &meta.Offset, + }, + } + + cache := make(map[int64]string) + for _, cl := range clusters { + servers, err := h.db.GetClusterServers(param.HTTPRequest.Context(), cl.ID) + if err != nil { + return cluster.NewGetClustersBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + environmentCode, err := h.getEnvironmentCode(param.HTTPRequest.Context(), cl.EnvironmentID, cache) + if err != nil { + return cluster.NewGetClustersBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + clustersResp.Data = append(clustersResp.Data, convert.ClusterToSwagger(&cl, servers, environmentCode, project.Name)) + } + + return cluster.NewGetClustersOK().WithPayload(&clustersResp) +} + +func (h *getClustersHandler) getEnvironmentCode(ctx context.Context, environmentID int64, cache map[int64]string) (string, error) { + code, ok := cache[environmentID] + if ok { + return code, nil + } + + environment, err := h.db.GetEnvironment(ctx, environmentID) + if err != nil { + return "", err + } + + cache[environmentID] = environment.Name + + return environment.Name, nil +} diff --git a/console/service/internal/controllers/cluster/post_cluster.go b/console/service/internal/controllers/cluster/post_cluster.go new file mode 100644 index 000000000..b8c015ab0 --- /dev/null +++ b/console/service/internal/controllers/cluster/post_cluster.go @@ -0,0 +1,248 @@ +package cluster + +import ( + "encoding/json" + "fmt" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "github.com/segmentio/asm/base64" + "go.openly.dev/pointy" + "postgesql-cluster-console/internal/configuration" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/internal/watcher" + "postgesql-cluster-console/internal/xdocker" + "postgesql-cluster-console/models" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/cluster" + "strconv" + "strings" +) + +type postClusterHandler struct { + db storage.IStorage + dockerManager xdocker.IManager + logCollector watcher.LogCollector + log zerolog.Logger + cfg *configuration.Config +} + +func NewPostClusterHandler(db storage.IStorage, dockerManager xdocker.IManager, logCollector watcher.LogCollector, cfg *configuration.Config, log zerolog.Logger) cluster.PostClustersHandler { + return &postClusterHandler{ + db: db, + dockerManager: dockerManager, + logCollector: logCollector, + log: log, + cfg: cfg, + } +} + +func (h *postClusterHandler) Handle(param cluster.PostClustersParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + oldCluster, err := h.db.GetClusterByName(param.HTTPRequest.Context(), param.Body.Name) + if err != nil { + localLog.Warn().Err(err).Msg("can't get cluster by name") + } + if oldCluster != nil { + localLog.Trace().Any("old_cluster", oldCluster).Msg("cluster already exists") + + return cluster.NewPostClustersBadRequest().WithPayload(controllers.MakeErrorPayload(fmt.Errorf("cluster %s already exists", param.Body.Name), controllers.BaseError)) + } + + var ( + secretEnvs []string + secretID *int64 + paramLocation ParamLocation + ) + if param.Body.AuthInfo != nil { + secretEnvs, paramLocation, err = getSecretEnvs(param.HTTPRequest.Context(), h.log, h.db, param.Body.AuthInfo.SecretID, h.cfg.EncryptionKey) + if err != nil { + localLog.Error().Err(err).Msg("failed to get secret") + + return cluster.NewPostClustersBadRequest().WithPayload(controllers.MakeErrorPayload(fmt.Errorf("failed to get secret: %s", err.Error()), controllers.BaseError)) + } + secretID = ¶m.Body.AuthInfo.SecretID + localLog.Trace().Strs("secretEnvs", secretEnvs).Msg("got secret") + } else { + localLog.Debug().Msg("AuthInfo is nil, secret is expected in envs from web") + } + + ansibleLogEnv := h.getAnsibleLogEnv(param.Body.Name) + localLog.Trace().Strs("file_log", ansibleLogEnv).Msg("got file log name") + + if paramLocation == EnvParamLocation { + param.Body.Envs = append(param.Body.Envs, secretEnvs...) + } else if paramLocation == ExtraVarsParamLocation { + param.Body.ExtraVars = append(param.Body.ExtraVars, secretEnvs...) + } + param.Body.Envs = append(param.Body.Envs, ansibleLogEnv...) + param.Body.ExtraVars = append(param.Body.ExtraVars, "patroni_cluster_name="+param.Body.Name) + + h.addProxySettings(¶m, localLog) + + const ( + LocationExtraVar = "server_location" + CloudProviderExtraVar = "cloud_provider" + ServersExtraVar = "server_count" + PostgreSqlVersionExtraVar = "postgresql_version" + InventoryJsonEnv = "ANSIBLE_INVENTORY_JSON" + ) + + var ( + serverCount int + inventoryJsonVal []byte + ) + + if getValFromVars(param.Body.ExtraVars, CloudProviderExtraVar) == "" { + inventoryJsonVal = []byte(getValFromVars(param.Body.Envs, InventoryJsonEnv)) + var inventoryJson InventoryJson + err = json.Unmarshal(inventoryJsonVal, &inventoryJson) + if err != nil { + localLog.Debug().Err(err).Str("inventory_json_val", string(inventoryJsonVal)).Msg("failed to parse inventory json, try to base64 decode") + inventoryJsonVal, err = base64.StdEncoding.DecodeString(string(inventoryJsonVal)) + if err != nil { + localLog.Debug().Err(err).Msg("failed to base64 decode inventory json") + inventoryJsonVal = nil // to correct insert in db + } else { + err = json.Unmarshal(inventoryJsonVal, &inventoryJson) + if err != nil { + localLog.Debug().Err(err).Str("inventory_json_val", string(inventoryJsonVal)).Msg("failed to parse inventory json") + inventoryJsonVal = nil // to correct insert to db + } else { + serverCount = len(inventoryJson.All.Children.Master.Hosts) + len(inventoryJson.All.Children.Replica.Hosts) + } + } + } else { + serverCount = len(inventoryJson.All.Children.Master.Hosts) + len(inventoryJson.All.Children.Replica.Hosts) + } + } else { + serverCount = getIntValFromVars(param.Body.ExtraVars, ServersExtraVar) + } + + createdCluster, err := h.db.CreateCluster(param.HTTPRequest.Context(), &storage.CreateClusterReq{ + ProjectID: param.Body.ProjectID, + EnvironmentID: param.Body.EnvironmentID, + Name: param.Body.Name, + Description: param.Body.Description, + SecretID: secretID, + ExtraVars: param.Body.ExtraVars, + Location: getValFromVars(param.Body.ExtraVars, LocationExtraVar), + ServerCount: serverCount, + PostgreSqlVersion: getIntValFromVars(param.Body.ExtraVars, PostgreSqlVersionExtraVar), + Status: "deploying", + Inventory: inventoryJsonVal, + }) + if err != nil { + return cluster.NewPostClustersBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + localLog.Info().Any("cluster", createdCluster).Msg("cluster was created") + + defer func() { + if err != nil { + _, err = h.db.UpdateCluster(param.HTTPRequest.Context(), &storage.UpdateClusterReq{ + ID: createdCluster.ID, + Status: pointy.String(storage.ClusterStatusFailed), + }) + if err != nil { + localLog.Error().Err(err).Msg("failed to update cluster") + } + } + }() + + var dockerId xdocker.InstanceID + dockerId, err = h.dockerManager.ManageCluster(param.HTTPRequest.Context(), &xdocker.ManageClusterConfig{ + Envs: param.Body.Envs, + ExtraVars: param.Body.ExtraVars, + Mounts: []xdocker.Mount{ + { + DockerPath: ansibleLogDir, + HostPath: h.cfg.Docker.LogDir, + }, + }, + }) + if err != nil { + return cluster.NewPostClustersBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + localLog.Info().Str("docker_id", string(dockerId)).Msg("docker was started") + + var createdOperation *storage.Operation + createdOperation, err = h.db.CreateOperation(param.HTTPRequest.Context(), &storage.CreateOperationReq{ + ProjectID: param.Body.ProjectID, + ClusterID: createdCluster.ID, + DockerCode: string(dockerId), + Type: storage.OperationTypeDeploy, + Cid: cid, + }) + if err != nil { + return cluster.NewPostClustersBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + localLog.Info().Any("operation", createdOperation).Msg("operation was created") + h.logCollector.StoreInDb(createdOperation.ID, dockerId, cid) + + return cluster.NewPostClustersOK().WithPayload(&models.ResponseClusterCreate{ + ClusterID: createdCluster.ID, + OperationID: createdOperation.ID, + }) +} + +func (h *postClusterHandler) addProxySettings(param *cluster.PostClustersParams, localLog zerolog.Logger) { + const proxySettingName = "proxy_env" + proxySetting, err := h.db.GetSettingByName(param.HTTPRequest.Context(), proxySettingName) + if err != nil { + localLog.Warn().Err(err).Msg("failed to get proxy setting") + } + if proxySetting != nil { + proxySettingVal, err := json.Marshal(proxySetting.Value) + if err != nil { + localLog.Error().Any("proxy_env", proxySetting.Value).Err(err).Msg("failed to marshal proxy_env") + } else { + param.Body.ExtraVars = append(param.Body.ExtraVars, proxySettingName+"="+string(proxySettingVal)) + localLog.Info().Str("proxy_env", string(proxySettingVal)).Msg("proxy_env was added to --extra-vars") + } + } +} + +const ansibleLogDir = "/tmp/ansible" + +func (h *postClusterHandler) getAnsibleLogEnv(clusterName string) []string { + return []string{"ANSIBLE_JSON_LOG_FILE=" + ansibleLogDir + "/" + clusterName + ".json"} +} + +func getValFromVars(vars []string, key string) string { + for _, extraVar := range vars { + if strings.HasPrefix(strings.ToLower(extraVar), strings.ToLower(key)) { + keyVal := strings.Split(extraVar, "=") + if len(keyVal) != 2 { + return "" + } + + return keyVal[1] + } + } + + return "" +} + +func getIntValFromVars(vars []string, key string) int { + valStr := getValFromVars(vars, key) + valInt, err := strconv.Atoi(valStr) + if err != nil { + return 0 + } + + return valInt +} + +type InventoryJson struct { + All struct { + Children struct { + Master struct { + Hosts map[string]interface{} `json:"hosts"` + } `json:"master"` + Replica struct { + Hosts map[string]interface{} `json:"hosts"` + } `json:"replica"` + } `json:"children"` + } `json:"all"` +} diff --git a/console/service/internal/controllers/cluster/post_cluster_refresh.go b/console/service/internal/controllers/cluster/post_cluster_refresh.go new file mode 100644 index 000000000..85d66173d --- /dev/null +++ b/console/service/internal/controllers/cluster/post_cluster_refresh.go @@ -0,0 +1,40 @@ +package cluster + +import ( + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/internal/watcher" + "postgesql-cluster-console/restapi/operations/cluster" +) + +type postClusterRefreshHandler struct { + db storage.IStorage + log zerolog.Logger + clusterWatcher watcher.ClusterWatcher +} + +func NewPostClusterRefreshHandler(db storage.IStorage, log zerolog.Logger, clusterWatcher watcher.ClusterWatcher) cluster.PostClustersIDRefreshHandler { + return &postClusterRefreshHandler{ + db: db, + log: log, + clusterWatcher: clusterWatcher, + } +} + +func (h *postClusterRefreshHandler) Handle(param cluster.PostClustersIDRefreshParams) middleware.Responder { + cl, err := h.db.GetCluster(param.HTTPRequest.Context(), param.ID) + if err != nil { + return cluster.NewPostClustersIDRefreshBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + h.clusterWatcher.HandleCluster(param.HTTPRequest.Context(), cl) + + resp, err := getClusterInfo(param.HTTPRequest.Context(), h.db, cl) + if err != nil { + return cluster.NewPostClustersIDRefreshBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return cluster.NewPostClustersIDRefreshOK().WithPayload(resp) +} diff --git a/console/service/internal/controllers/cluster/remove_cluster.go b/console/service/internal/controllers/cluster/remove_cluster.go new file mode 100644 index 000000000..840becad5 --- /dev/null +++ b/console/service/internal/controllers/cluster/remove_cluster.go @@ -0,0 +1,87 @@ +package cluster + +import ( + "encoding/base64" + "encoding/json" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/configuration" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/internal/watcher" + "postgesql-cluster-console/internal/xdocker" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/cluster" +) + +type removeClusterHandler struct { + db storage.IStorage + dockerManager xdocker.IManager + logCollector watcher.LogCollector + log zerolog.Logger + cfg *configuration.Config +} + +func NewRemoveClusterHandler(db storage.IStorage, dockerManager xdocker.IManager, logCollector watcher.LogCollector, cfg *configuration.Config, log zerolog.Logger) cluster.PostClustersIDRemoveHandler { + return &removeClusterHandler{ + db: db, + dockerManager: dockerManager, + logCollector: logCollector, + log: log, + cfg: cfg, + } +} + +func (h *removeClusterHandler) Handle(param cluster.PostClustersIDRemoveParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + clusterInfo, err := h.db.GetCluster(param.HTTPRequest.Context(), param.ID) + if err != nil { + return cluster.NewPostClustersIDRemoveBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + var extraVars []string + + err = json.Unmarshal(clusterInfo.ExtraVars, &extraVars) + if err != nil { + return cluster.NewPostClustersIDRemoveBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + extraVars = append(extraVars, "state=absent") + + var ( + envs []string + paramLocation ParamLocation + ) + if clusterInfo.SecretID != nil { + envs, paramLocation, err = getSecretEnvs(param.HTTPRequest.Context(), h.log, h.db, *clusterInfo.SecretID, h.cfg.EncryptionKey) + if err != nil { + return cluster.NewPostClustersIDRemoveBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + if paramLocation == ExtraVarsParamLocation { + extraVars = append(extraVars, envs...) + } + } + envs = append(envs, "patroni_cluster_name="+clusterInfo.Name) + if len(clusterInfo.Inventory) != 0 { + envs = append(envs, "ANSIBLE_INVENTORY_JSON="+base64.StdEncoding.EncodeToString(clusterInfo.Inventory)) + } + localLog.Trace().Strs("envs", envs).Msg("got envs") + + dockerId, err := h.dockerManager.ManageCluster(param.HTTPRequest.Context(), &xdocker.ManageClusterConfig{ + Envs: envs, + ExtraVars: extraVars, + }) + if err != nil { + return cluster.NewPostClustersIDRemoveBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + localLog.Trace().Str("docker_id", string(dockerId)).Msg("docker was started") + + err = h.db.DeleteCluster(param.HTTPRequest.Context(), clusterInfo.ID) + if err != nil { + return cluster.NewPostClustersIDRemoveBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + h.logCollector.PrintToConsole(dockerId, cid) + + return cluster.NewPostClustersIDRemoveNoContent() +} diff --git a/console/service/internal/controllers/cluster/utils.go b/console/service/internal/controllers/cluster/utils.go new file mode 100644 index 000000000..703caded8 --- /dev/null +++ b/console/service/internal/controllers/cluster/utils.go @@ -0,0 +1,98 @@ +package cluster + +import ( + "context" + "encoding/json" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/pkg/tracer" +) + +type ParamLocation uint8 + +const ( + UnknownParamLocation ParamLocation = 0 + EnvParamLocation ParamLocation = 1 + ExtraVarsParamLocation ParamLocation = 2 +) + +func getSecretEnvs(ctx context.Context, log zerolog.Logger, db storage.IStorage, secretID int64, secretKey string) ([]string, ParamLocation, error) { + localLog := log.With().Str("cid", ctx.Value(tracer.CtxCidKey{}).(string)).Logger() + secretView, err := db.GetSecret(ctx, secretID) + if err != nil { + return nil, UnknownParamLocation, err + } + localLog.Trace().Any("secret_view", secretView).Msg("got secret view from db") + secretVal, err := db.GetSecretVal(ctx, secretID, secretKey) + if err != nil { + return nil, UnknownParamLocation, err + } + localLog.Trace().Msgf("secretVal %s", string(secretVal)) + + switch models.SecretType(secretView.Type) { + case models.SecretTypeAws: + var sec models.RequestSecretValueAws + err = json.Unmarshal(secretVal, &sec) + if err != nil { + return nil, UnknownParamLocation, err + } + + return []string{"AWS_ACCESS_KEY_ID=" + sec.AWSACCESSKEYID, "AWS_SECRET_ACCESS_KEY=" + sec.AWSSECRETACCESSKEY}, EnvParamLocation, nil + case models.SecretTypeGcp: + var sec models.RequestSecretValueGcp + err = json.Unmarshal(secretVal, &sec) + if err != nil { + return nil, UnknownParamLocation, err + } + + return []string{"GCP_SERVICE_ACCOUNT_CONTENTS=" + sec.GCPSERVICEACCOUNTCONTENTS}, EnvParamLocation, nil + case models.SecretTypeAzure: + var sec models.RequestSecretValueAzure + err = json.Unmarshal(secretVal, &sec) + if err != nil { + return nil, UnknownParamLocation, err + } + + return []string{ + "AZURE_SUBSCRIPTION_ID=" + sec.AZURESUBSCRIPTIONID, + "AZURE_CLIENT_ID=" + sec.AZURECLIENTID, + "AZURE_SECRET=" + sec.AZURESECRET, + "AZURE_TENANT=" + sec.AZURETENANT, + }, EnvParamLocation, nil + case models.SecretTypeDigitalocean: + var sec models.RequestSecretValueDigitalOcean + err = json.Unmarshal(secretVal, &sec) + if err != nil { + return nil, UnknownParamLocation, err + } + + return []string{"DO_API_TOKEN=" + sec.DOAPITOKEN}, EnvParamLocation, nil + case models.SecretTypeHetzner: + var sec models.RequestSecretValueHetzner + err = json.Unmarshal(secretVal, &sec) + if err != nil { + return nil, UnknownParamLocation, err + } + + return []string{"HCLOUD_API_TOKEN=" + sec.HCLOUDAPITOKEN}, EnvParamLocation, nil + case models.SecretTypeSSHKey: + var sec models.RequestSecretValueSSHKey + err = json.Unmarshal(secretVal, &sec) + if err != nil { + return nil, UnknownParamLocation, err + } + + return []string{"SSH_PRIVATE_KEY_CONTENT=" + sec.SSHPRIVATEKEY}, EnvParamLocation, nil + case models.SecretTypePassword: + var sec models.RequestSecretValuePassword + err = json.Unmarshal(secretVal, &sec) + if err != nil { + return nil, UnknownParamLocation, err + } + + return []string{"ansible_user=" + sec.USERNAME, "ansible_ssh_pass=" + sec.PASSWORD, "ansible_sudo_pass=" + sec.PASSWORD}, ExtraVarsParamLocation, nil + default: + return nil, UnknownParamLocation, nil + } +} diff --git a/console/service/internal/controllers/dictionary/get_database_extensions.go b/console/service/internal/controllers/dictionary/get_database_extensions.go new file mode 100644 index 000000000..b8f2fab07 --- /dev/null +++ b/console/service/internal/controllers/dictionary/get_database_extensions.go @@ -0,0 +1,41 @@ +package dictionary + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/dictionary" +) + +type getDbExtensionsHandler struct { + db storage.IStorage +} + +func NewGetDbExtensionsHandler(db storage.IStorage) dictionary.GetDatabaseExtensionsHandler { + return &getDbExtensionsHandler{ + db: db, + } +} + +func (h *getDbExtensionsHandler) Handle(param dictionary.GetDatabaseExtensionsParams) middleware.Responder { + extensions, meta, err := h.db.GetExtensions(param.HTTPRequest.Context(), &storage.GetExtensionsReq{ + Type: param.ExtensionType, + PostgresVersion: param.PostgresVersion, + Limit: param.Limit, + Offset: param.Offset, + }) + if err != nil { + return dictionary.NewGetDatabaseExtensionsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return dictionary.NewGetDatabaseExtensionsOK().WithPayload(&models.ResponseDatabaseExtensions{ + Data: convert.DbExtensionsToSwagger(extensions), + Meta: &models.MetaPagination{ + Count: &meta.Count, + Limit: &meta.Limit, + Offset: &meta.Offset, + }, + }) +} diff --git a/console/service/internal/controllers/dictionary/get_external_deployments.go b/console/service/internal/controllers/dictionary/get_external_deployments.go new file mode 100644 index 000000000..565351e77 --- /dev/null +++ b/console/service/internal/controllers/dictionary/get_external_deployments.go @@ -0,0 +1,46 @@ +package dictionary + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/dictionary" +) + +type getExternalDeploymentsHandler struct { + db storage.IStorage +} + +func NewGetExternalDeploymentsHandler(db storage.IStorage) dictionary.GetExternalDeploymentsHandler { + return &getExternalDeploymentsHandler{ + db: db, + } +} + +func (h *getExternalDeploymentsHandler) Handle(param dictionary.GetExternalDeploymentsParams) middleware.Responder { + cloudProviders, metaPagination, err := h.db.GetCloudProviders(param.HTTPRequest.Context(), param.Limit, param.Offset) + if err != nil { + return dictionary.NewGetExternalDeploymentsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + resp := &models.ResponseDeploymentsInfo{ + Data: make([]*models.ResponseDeploymentInfo, 0, len(cloudProviders)), + Meta: &models.MetaPagination{ + Count: &metaPagination.Count, + Limit: &metaPagination.Limit, + Offset: &metaPagination.Offset, + }, + } + for _, cloudProvider := range cloudProviders { + cloudProviderInfo, err := h.db.GetCloudProviderInfo(param.HTTPRequest.Context(), cloudProvider.Code) + if err != nil { + return dictionary.NewGetDatabaseExtensionsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + resp.Data = append(resp.Data, convert.ProviderInfoToSwagger(cloudProviderInfo, cloudProvider.Description, cloudProvider.ProviderImage)) + } + + return dictionary.NewGetExternalDeploymentsOK().WithPayload(resp) +} diff --git a/console/service/internal/controllers/dictionary/get_postgres_versions.go b/console/service/internal/controllers/dictionary/get_postgres_versions.go new file mode 100644 index 000000000..d03cddb81 --- /dev/null +++ b/console/service/internal/controllers/dictionary/get_postgres_versions.go @@ -0,0 +1,31 @@ +package dictionary + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/dictionary" +) + +type getPostgresVersionsHandler struct { + db storage.IStorage +} + +func NewGetPostgresVersions(db storage.IStorage) dictionary.GetPostgresVersionsHandler { + return &getPostgresVersionsHandler{ + db: db, + } +} + +func (h *getPostgresVersionsHandler) Handle(param dictionary.GetPostgresVersionsParams) middleware.Responder { + postgresVersions, err := h.db.GetPostgresVersions(param.HTTPRequest.Context()) + if err != nil { + return dictionary.NewGetPostgresVersionsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return dictionary.NewGetPostgresVersionsOK().WithPayload(&models.ResponsePostgresVersions{ + Data: convert.PostgresVersions(postgresVersions), + }) +} diff --git a/console/service/internal/controllers/environment/delete_environment.go b/console/service/internal/controllers/environment/delete_environment.go new file mode 100644 index 000000000..78bb7e16e --- /dev/null +++ b/console/service/internal/controllers/environment/delete_environment.go @@ -0,0 +1,40 @@ +package environment + +import ( + "errors" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/environment" +) + +type deleteEnvironmentsHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewDeleteEnvironmentsHandler(db storage.IStorage, log zerolog.Logger) environment.DeleteEnvironmentsIDHandler { + return &deleteEnvironmentsHandler{ + db: db, + log: log, + } +} + +func (h *deleteEnvironmentsHandler) Handle(param environment.DeleteEnvironmentsIDParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + isUsed, err := h.db.CheckEnvironmentIsUsed(param.HTTPRequest.Context(), param.ID) + if err != nil { + localLog.Warn().Err(err).Msg("failed to check that environment is used") + } else if isUsed { + return environment.NewDeleteEnvironmentsIDBadRequest().WithPayload(controllers.MakeErrorPayload(errors.New("The environment is used"), controllers.BaseError)) + } + err = h.db.DeleteEnvironment(param.HTTPRequest.Context(), param.ID) + if err != nil { + return environment.NewDeleteEnvironmentsIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return environment.NewDeleteEnvironmentsIDNoContent() +} diff --git a/console/service/internal/controllers/environment/get_environments.go b/console/service/internal/controllers/environment/get_environments.go new file mode 100644 index 000000000..206dd3f0f --- /dev/null +++ b/console/service/internal/controllers/environment/get_environments.go @@ -0,0 +1,36 @@ +package environment + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/environment" +) + +type getEnvironmentsHandler struct { + db storage.IStorage +} + +func NewGetEnvironmentsHandler(db storage.IStorage) environment.GetEnvironmentsHandler { + return &getEnvironmentsHandler{ + db: db, + } +} + +func (h *getEnvironmentsHandler) Handle(param environment.GetEnvironmentsParams) middleware.Responder { + environments, meta, err := h.db.GetEnvironments(param.HTTPRequest.Context(), param.Limit, param.Offset) + if err != nil { + return environment.NewGetEnvironmentsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return environment.NewGetEnvironmentsOK().WithPayload(&models.ResponseEnvironmentsList{ + Data: convert.EnvironmentsToSwagger(environments), + Meta: &models.MetaPagination{ + Count: &meta.Count, + Limit: &meta.Limit, + Offset: &meta.Offset, + }, + }) +} diff --git a/console/service/internal/controllers/environment/post_environment.go b/console/service/internal/controllers/environment/post_environment.go new file mode 100644 index 000000000..ed916ffc7 --- /dev/null +++ b/console/service/internal/controllers/environment/post_environment.go @@ -0,0 +1,44 @@ +package environment + +import ( + "fmt" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/environment" +) + +type postEnvironmentsHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewPostEnvironmentsHandler(db storage.IStorage, log zerolog.Logger) environment.PostEnvironmentsHandler { + return &postEnvironmentsHandler{ + db: db, + log: log, + } +} + +func (h *postEnvironmentsHandler) Handle(param environment.PostEnvironmentsParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + checkEnv, err := h.db.GetEnvironmentByName(param.HTTPRequest.Context(), param.Body.Name) + if err != nil { + localLog.Warn().Err(err).Msg("failed to check environment name exists") + } else if checkEnv != nil { + return environment.NewPostEnvironmentsBadRequest().WithPayload(controllers.MakeErrorPayload(fmt.Errorf("The environment named %q already exists", param.Body.Name), controllers.BaseError)) + } + env, err := h.db.CreateEnvironment(param.HTTPRequest.Context(), &storage.AddEnvironmentReq{ + Name: param.Body.Name, + Description: param.Body.Description, + }) + if err != nil { + return environment.NewPostEnvironmentsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return environment.NewPostEnvironmentsOK().WithPayload(convert.EnvironmentToSwagger(env)) +} diff --git a/console/service/internal/controllers/errors.go b/console/service/internal/controllers/errors.go new file mode 100644 index 000000000..916d930d1 --- /dev/null +++ b/console/service/internal/controllers/errors.go @@ -0,0 +1,5 @@ +package controllers + +const ( + BaseError = int64(100) +) diff --git a/console/service/internal/controllers/operation/get_operation_log.go b/console/service/internal/controllers/operation/get_operation_log.go new file mode 100644 index 000000000..f32ee0daf --- /dev/null +++ b/console/service/internal/controllers/operation/get_operation_log.go @@ -0,0 +1,34 @@ +package operation + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/restapi/operations/operation" +) + +type getOperationLogHandler struct { + db storage.IStorage +} + +func NewGetOperationLogHandler(db storage.IStorage) operation.GetOperationsIDLogHandler { + return &getOperationLogHandler{ + db: db, + } +} + +func (h *getOperationLogHandler) Handle(param operation.GetOperationsIDLogParams) middleware.Responder { + op, err := h.db.GetOperation(param.HTTPRequest.Context(), param.ID) + if err != nil { + return operation.NewGetOperationsIDLogBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + var logMessage string + if op.Log != nil { + logMessage = *op.Log + } + + return operation.NewGetOperationsIDLogOK().WithPayload(logMessage).WithContentType("plain/text").WithXLogCompleted(func() bool { + return op.Status != storage.OperationStatusInProgress + }()) +} diff --git a/console/service/internal/controllers/operation/get_operations.go b/console/service/internal/controllers/operation/get_operations.go new file mode 100644 index 000000000..7fed2e9e3 --- /dev/null +++ b/console/service/internal/controllers/operation/get_operations.go @@ -0,0 +1,47 @@ +package operation + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/operation" + "time" +) + +type getOperationsHandler struct { + db storage.IStorage +} + +func NewGetOperationsHandler(db storage.IStorage) operation.GetOperationsHandler { + return &getOperationsHandler{ + db: db, + } +} + +func (h *getOperationsHandler) Handle(param operation.GetOperationsParams) middleware.Responder { + operations, meta, err := h.db.GetOperations(param.HTTPRequest.Context(), &storage.GetOperationsReq{ + ProjectID: param.ProjectID, + StartedFrom: time.Time(param.StartDate), + EndedTill: time.Time(param.EndDate), + ClusterName: param.ClusterName, + Type: param.Type, + Status: param.Status, + SortBy: param.SortBy, + Limit: param.Limit, + Offset: param.Offset, + }) + if err != nil { + return operation.NewGetOperationsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return operation.NewGetOperationsOK().WithPayload(&models.ResponseOperationsList{ + Data: convert.OperationsViewToSwagger(operations), + Meta: &models.MetaPagination{ + Count: &meta.Count, + Limit: &meta.Limit, + Offset: &meta.Offset, + }, + }) +} diff --git a/console/service/internal/controllers/project/delete_project.go b/console/service/internal/controllers/project/delete_project.go new file mode 100644 index 000000000..f9af029c6 --- /dev/null +++ b/console/service/internal/controllers/project/delete_project.go @@ -0,0 +1,61 @@ +package project + +import ( + "fmt" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/project" + "strings" +) + +type deleteProjectHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewDeleteProjectHandler(db storage.IStorage, log zerolog.Logger) project.DeleteProjectsIDHandler { + return &deleteProjectHandler{ + db: db, + log: log, + } +} + +func (h *deleteProjectHandler) Handle(param project.DeleteProjectsIDParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + checkClusters, _, err := h.db.GetClusters(param.HTTPRequest.Context(), &storage.GetClustersReq{ + ProjectID: param.ID, + }) + if err != nil { + localLog.Warn().Err(err).Msg("failed to check that project is used") + } else if len(checkClusters) != 0 { + return project.NewDeleteProjectsIDBadRequest().WithPayload(controllers.MakeErrorPayload(fmt.Errorf("The project is used by %d cluster(s) (%s)", len(checkClusters), getClustersNameTitle(checkClusters)), controllers.BaseError)) + } + err = h.db.DeleteProject(param.HTTPRequest.Context(), param.ID) + if err != nil { + return project.NewDeleteProjectsIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return project.NewDeleteProjectsIDNoContent() +} + +func getClustersNameTitle(clusters []storage.Cluster) string { + const maxSize = 3 + title := strings.Builder{} + for i, cl := range clusters { + if i >= maxSize { + title.WriteString(",...") + + return title.String() + } + if i != 0 { + title.WriteString(",") + } + title.WriteString(cl.Name) + } + + return title.String() +} diff --git a/console/service/internal/controllers/project/get_projects.go b/console/service/internal/controllers/project/get_projects.go new file mode 100644 index 000000000..feacbf346 --- /dev/null +++ b/console/service/internal/controllers/project/get_projects.go @@ -0,0 +1,36 @@ +package project + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/project" +) + +type getProjectsHandler struct { + db storage.IStorage +} + +func NewGetProjectsHandler(db storage.IStorage) project.GetProjectsHandler { + return &getProjectsHandler{ + db: db, + } +} + +func (h *getProjectsHandler) Handle(param project.GetProjectsParams) middleware.Responder { + projects, meta, err := h.db.GetProjects(param.HTTPRequest.Context(), param.Limit, param.Offset) + if err != nil { + return project.NewGetProjectsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return project.NewGetProjectsOK().WithPayload(&models.ResponseProjectsList{ + Data: convert.ProjectsToSwagger(projects), + Meta: &models.MetaPagination{ + Count: &meta.Count, + Limit: &meta.Limit, + Offset: &meta.Offset, + }, + }) +} diff --git a/console/service/internal/controllers/project/path_project.go b/console/service/internal/controllers/project/path_project.go new file mode 100644 index 000000000..e9e114bf0 --- /dev/null +++ b/console/service/internal/controllers/project/path_project.go @@ -0,0 +1,28 @@ +package project + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/restapi/operations/project" +) + +type patchProjectHandler struct { + db storage.IStorage +} + +func NewPatchProjectHandler(db storage.IStorage) project.PatchProjectsIDHandler { + return &patchProjectHandler{ + db: db, + } +} + +func (h *patchProjectHandler) Handle(param project.PatchProjectsIDParams) middleware.Responder { + updatedProject, err := h.db.UpdateProject(param.HTTPRequest.Context(), param.ID, param.Body.Name, param.Body.Description) + if err != nil { + return project.NewPatchProjectsIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return project.NewPatchProjectsIDOK().WithPayload(convert.ProjectToSwagger(updatedProject)) +} diff --git a/console/service/internal/controllers/project/post_project.go b/console/service/internal/controllers/project/post_project.go new file mode 100644 index 000000000..efcf037a6 --- /dev/null +++ b/console/service/internal/controllers/project/post_project.go @@ -0,0 +1,42 @@ +package project + +import ( + "fmt" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/project" +) + +type postProjectHandler struct { + db storage.IStorage + log zerolog.Logger +} + +func NewPostProjectHandler(db storage.IStorage, log zerolog.Logger) project.PostProjectsHandler { + return &postProjectHandler{ + db: db, + log: log, + } +} + +func (h *postProjectHandler) Handle(param project.PostProjectsParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + checkProject, err := h.db.GetProjectByName(param.HTTPRequest.Context(), param.Body.Name) + if err != nil { + localLog.Warn().Err(err).Msg("failed to check project name exists") + } else if checkProject != nil { + return project.NewPostProjectsBadRequest().WithPayload(controllers.MakeErrorPayload(fmt.Errorf("The project %q named already exists", param.Body.Name), controllers.BaseError)) + } + + createdProject, err := h.db.CreateProject(param.HTTPRequest.Context(), param.Body.Name, param.Body.Description) + if err != nil { + return project.NewPostProjectsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return project.NewPostProjectsOK().WithPayload(convert.ProjectToSwagger(createdProject)) +} diff --git a/console/service/internal/controllers/secret/delete_secret.go b/console/service/internal/controllers/secret/delete_secret.go new file mode 100644 index 000000000..2569feb24 --- /dev/null +++ b/console/service/internal/controllers/secret/delete_secret.go @@ -0,0 +1,27 @@ +package secret + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/restapi/operations/secret" +) + +type deleteSecretHandler struct { + db storage.IStorage +} + +func NewDeleteSecretHandler(db storage.IStorage) secret.DeleteSecretsIDHandler { + return &deleteSecretHandler{ + db: db, + } +} + +func (h *deleteSecretHandler) Handle(param secret.DeleteSecretsIDParams) middleware.Responder { + err := h.db.DeleteSecret(param.HTTPRequest.Context(), param.ID) + if err != nil { + return secret.NewDeleteSecretsIDBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return secret.NewDeleteSecretsIDNoContent() +} diff --git a/console/service/internal/controllers/secret/get_secrets.go b/console/service/internal/controllers/secret/get_secrets.go new file mode 100644 index 000000000..36217e2d9 --- /dev/null +++ b/console/service/internal/controllers/secret/get_secrets.go @@ -0,0 +1,43 @@ +package secret + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/secret" +) + +type getSecretHandler struct { + db storage.IStorage +} + +func NewGetSecretHandler(db storage.IStorage) secret.GetSecretsHandler { + return &getSecretHandler{ + db: db, + } +} + +func (h *getSecretHandler) Handle(param secret.GetSecretsParams) middleware.Responder { + secrets, meta, err := h.db.GetSecrets(param.HTTPRequest.Context(), &storage.GetSecretsReq{ + ProjectID: param.ProjectID, + Name: param.Name, + Type: param.Type, + SortBy: param.SortBy, + Limit: param.Limit, + Offset: param.Offset, + }) + if err != nil { + return secret.NewGetSecretsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return secret.NewGetSecretsOK().WithPayload(&models.ResponseSecretInfoList{ + Data: convert.SecretsViewToSwagger(secrets), + Meta: &models.MetaPagination{ + Count: &meta.Count, + Limit: &meta.Limit, + Offset: &meta.Offset, + }, + }) +} diff --git a/console/service/internal/controllers/secret/post_secret.go b/console/service/internal/controllers/secret/post_secret.go new file mode 100644 index 000000000..3cc4ad46e --- /dev/null +++ b/console/service/internal/controllers/secret/post_secret.go @@ -0,0 +1,76 @@ +package secret + +import ( + "encoding/json" + "fmt" + "github.com/go-openapi/runtime/middleware" + "github.com/rs/zerolog" + "postgesql-cluster-console/internal/configuration" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/pkg/tracer" + "postgesql-cluster-console/restapi/operations/secret" +) + +type postSecretHandler struct { + db storage.IStorage + log zerolog.Logger + cfg *configuration.Config +} + +func NewPostSecretHandler(db storage.IStorage, log zerolog.Logger, cfg *configuration.Config) secret.PostSecretsHandler { + return &postSecretHandler{ + db: db, + log: log, + cfg: cfg, + } +} + +func (h *postSecretHandler) Handle(param secret.PostSecretsParams) middleware.Responder { + cid := param.HTTPRequest.Context().Value(tracer.CtxCidKey{}).(string) + localLog := h.log.With().Str("cid", cid).Logger() + checkSecret, err := h.db.GetSecretByName(param.HTTPRequest.Context(), param.Body.Name) + if err != nil { + localLog.Warn().Err(err).Msg("failed to check secret name exists") + } else if checkSecret != nil { + return secret.NewPostSecretsBadRequest().WithPayload(controllers.MakeErrorPayload(fmt.Errorf("The secret named %q already exists", param.Body.Name), controllers.BaseError)) + } + + var ( + value []byte + ) + switch param.Body.Type { + case models.SecretTypeAws: + value, err = json.Marshal(param.Body.Value.Aws) + case models.SecretTypeGcp: + value, err = json.Marshal(param.Body.Value.Gcp) + case models.SecretTypeHetzner: + value, err = json.Marshal(param.Body.Value.Hetzner) + case models.SecretTypeSSHKey: + value, err = json.Marshal(param.Body.Value.SSHKey) + case models.SecretTypeDigitalocean: + value, err = json.Marshal(param.Body.Value.Digitalocean) + case models.SecretTypeAzure: + value, err = json.Marshal(param.Body.Value.Azure) + case models.SecretTypePassword: + value, err = json.Marshal(param.Body.Value.Password) + } + if err != nil { + return secret.NewPostSecretsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + createdSecret, err := h.db.CreateSecret(param.HTTPRequest.Context(), &storage.AddSecretReq{ + ProjectID: param.Body.ProjectID, + Type: string(param.Body.Type), + Name: param.Body.Name, + Value: value, + SecretKey: h.cfg.EncryptionKey, + }) + if err != nil { + return secret.NewPostSecretsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return secret.NewPostSecretsOK().WithPayload(convert.SecretViewToSwagger(createdSecret)) +} diff --git a/console/service/internal/controllers/setting/get_settings.go b/console/service/internal/controllers/setting/get_settings.go new file mode 100644 index 000000000..1af8997a5 --- /dev/null +++ b/console/service/internal/controllers/setting/get_settings.go @@ -0,0 +1,40 @@ +package setting + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi/operations/setting" +) + +type getSettingsHandler struct { + db storage.IStorage +} + +func NewGetSettingsHandler(db storage.IStorage) setting.GetSettingsHandler { + return &getSettingsHandler{ + db: db, + } +} + +func (h *getSettingsHandler) Handle(param setting.GetSettingsParams) middleware.Responder { + settings, meta, err := h.db.GetSettings(param.HTTPRequest.Context(), &storage.GetSettingsReq{ + Name: param.Name, + Limit: param.Limit, + Offset: param.Offset, + }) + if err != nil { + return setting.NewGetSettingsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return setting.NewGetSettingsOK().WithPayload(&models.ResponseSettings{ + Data: convert.SettingsToSwagger(settings), + Mete: &models.MetaPagination{ + Count: &meta.Count, + Limit: &meta.Limit, + Offset: &meta.Offset, + }, + }) +} diff --git a/console/service/internal/controllers/setting/patch_setting.go b/console/service/internal/controllers/setting/patch_setting.go new file mode 100644 index 000000000..c13181503 --- /dev/null +++ b/console/service/internal/controllers/setting/patch_setting.go @@ -0,0 +1,28 @@ +package setting + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/restapi/operations/setting" +) + +type patchSettingHandler struct { + db storage.IStorage +} + +func NewPatchSettingHandler(db storage.IStorage) setting.PatchSettingsNameHandler { + return &patchSettingHandler{ + db: db, + } +} + +func (h *patchSettingHandler) Handle(param setting.PatchSettingsNameParams) middleware.Responder { + s, err := h.db.UpdateSetting(param.HTTPRequest.Context(), param.Name, param.Body.Value) + if err != nil { + return setting.NewPatchSettingsNameBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return setting.NewPatchSettingsNameOK().WithPayload(convert.SettingToSwagger(s)) +} diff --git a/console/service/internal/controllers/setting/post_setting.go b/console/service/internal/controllers/setting/post_setting.go new file mode 100644 index 000000000..0345d34b9 --- /dev/null +++ b/console/service/internal/controllers/setting/post_setting.go @@ -0,0 +1,28 @@ +package setting + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/controllers" + "postgesql-cluster-console/internal/convert" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/restapi/operations/setting" +) + +type postSettingHandler struct { + db storage.IStorage +} + +func NewPostSettingHandler(db storage.IStorage) setting.PostSettingsHandler { + return &postSettingHandler{ + db: db, + } +} + +func (h *postSettingHandler) Handle(param setting.PostSettingsParams) middleware.Responder { + s, err := h.db.CreateSetting(param.HTTPRequest.Context(), param.Body.Name, param.Body.Value) + if err != nil { + return setting.NewPostSettingsBadRequest().WithPayload(controllers.MakeErrorPayload(err, controllers.BaseError)) + } + + return setting.NewPostSettingsOK().WithPayload(convert.SettingToSwagger(s)) +} diff --git a/console/service/internal/controllers/utils.go b/console/service/internal/controllers/utils.go new file mode 100644 index 000000000..d7b1903a1 --- /dev/null +++ b/console/service/internal/controllers/utils.go @@ -0,0 +1,11 @@ +package controllers + +import "postgesql-cluster-console/models" + +func MakeErrorPayload(err error, code int64) *models.ResponseError { + return &models.ResponseError{ + Code: code, + Description: err.Error(), + Title: err.Error(), + } +} diff --git a/console/service/internal/convert/clusters.go b/console/service/internal/convert/clusters.go new file mode 100644 index 000000000..879ab4824 --- /dev/null +++ b/console/service/internal/convert/clusters.go @@ -0,0 +1,45 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func ClusterToSwagger(cl *storage.Cluster, servers []storage.Server, environmentCode, projectCode string) *models.ClusterInfo { + clusterInfo := &models.ClusterInfo{ + ConnectionInfo: cl.ConnectionInfo, + CreationTime: strfmt.DateTime(cl.CreatedAt), + ClusterLocation: func() string { + if cl.Location != nil { + return *cl.Location + } + + return "" + }(), + Environment: environmentCode, + ID: cl.ID, + Servers: make([]*models.ClusterInfoInstance, 0, len(servers)), + Name: cl.Name, + Description: cl.Description, + PostgresVersion: cl.PostgreVersion, + ProjectName: projectCode, + Status: cl.Status, + } + + for _, server := range servers { + clusterInfo.Servers = append(clusterInfo.Servers, &models.ClusterInfoInstance{ + ID: server.ID, + IP: server.IpAddress.String(), + Lag: server.Lag, + Name: server.Name, + PendingRestart: server.PendingRestart, + Role: server.Role, + Status: server.Status, + Tags: server.Tags, + Timeline: server.Timeline, + }) + } + + return clusterInfo +} diff --git a/console/service/internal/convert/database_extensions.go b/console/service/internal/convert/database_extensions.go new file mode 100644 index 000000000..548a1dc41 --- /dev/null +++ b/console/service/internal/convert/database_extensions.go @@ -0,0 +1,27 @@ +package convert + +import ( + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func DbExtensionToSwagger(ext *storage.Extension) *models.ResponseDatabaseExtension { + return &models.ResponseDatabaseExtension{ + Contrib: ext.Contrib, + Description: ext.Description, + Image: ext.Image, + Name: ext.Name, + PostgresMaxVersion: ext.PostgresMaxVersion, + PostgresMinVersion: ext.PostgresMinVersion, + URL: ext.Url, + } +} + +func DbExtensionsToSwagger(exts []storage.Extension) []*models.ResponseDatabaseExtension { + resp := make([]*models.ResponseDatabaseExtension, 0, len(exts)) + for _, ext := range exts { + resp = append(resp, DbExtensionToSwagger(&ext)) + } + + return resp +} diff --git a/console/service/internal/convert/environments.go b/console/service/internal/convert/environments.go new file mode 100644 index 000000000..fdb582eac --- /dev/null +++ b/console/service/internal/convert/environments.go @@ -0,0 +1,33 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func EnvironmentToSwagger(env *storage.Environment) *models.ResponseEnvironment { + return &models.ResponseEnvironment{ + CreatedAt: strfmt.DateTime(env.CreatedAt), + Description: env.Description, + ID: env.ID, + Name: env.Name, + UpdatedAt: func() *strfmt.DateTime { + if env.UpdatedAt == nil { + return nil + } + updated := strfmt.DateTime(*env.UpdatedAt) + + return &updated + }(), + } +} + +func EnvironmentsToSwagger(envs []storage.Environment) []*models.ResponseEnvironment { + resp := make([]*models.ResponseEnvironment, 0, len(envs)) + for _, env := range envs { + resp = append(resp, EnvironmentToSwagger(&env)) + } + + return resp +} diff --git a/console/service/internal/convert/external_deployments.go b/console/service/internal/convert/external_deployments.go new file mode 100644 index 000000000..d29a812c0 --- /dev/null +++ b/console/service/internal/convert/external_deployments.go @@ -0,0 +1,112 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "go.openly.dev/pointy" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" + "sort" +) + +func ProviderInfoToSwagger(providerInfo *storage.CloudProviderInfo, description, image string) *models.ResponseDeploymentInfo { + resp := &models.ResponseDeploymentInfo{ + AvatarURL: image, + CloudRegions: nil, + Code: providerInfo.Code, + Description: description, + Volumes: nil, + InstanceTypes: &models.ResponseDeploymentInfoInstanceTypes{}, + } + + cloudRegions := make(map[string][]*models.DeploymentInfoCloudRegionDatacentersItems0) + for _, cloudRegion := range providerInfo.CloudRegions { + datacenterRegion := &models.DeploymentInfoCloudRegionDatacentersItems0{ + Code: cloudRegion.RegionName, + Location: cloudRegion.Description, + } + cloudImage := findCloudImage(providerInfo.CloudImages, cloudRegion.RegionName) + if cloudImage == nil { + cloudImage = findCloudImage(providerInfo.CloudImages, "all") + } + if cloudImage != nil { + datacenterRegion.CloudImage = &models.DeploymentCloudImage{ + Arch: cloudImage.Arch, + Image: cloudImage.Image, + OsName: cloudImage.OsName, + OsVersion: cloudImage.OsVersion, + UpdatedAt: strfmt.DateTime(cloudImage.UpdatedAt), + } + } + cloudRegions[cloudRegion.RegionGroup] = append(cloudRegions[cloudRegion.RegionGroup], datacenterRegion) + } + + mapKeys := make([]string, 0, len(cloudRegions)) + for k, _ := range cloudRegions { + mapKeys = append(mapKeys, k) + } + sort.Strings(mapKeys) + + for _, k := range mapKeys { + resp.CloudRegions = append(resp.CloudRegions, &models.DeploymentInfoCloudRegion{ + Code: k, + Datacenters: cloudRegions[k], + Name: k, + }) + } + + for _, instance := range providerInfo.CloudInstances { + switch instance.InstanceGroup { + case storage.InstanceTypeSmall: + resp.InstanceTypes.Small = append(resp.InstanceTypes.Small, &models.DeploymentInstanceType{ + Code: instance.InstanceName, + CPU: instance.Cpu, + PriceHourly: instance.PriceHourly, + PriceMonthly: instance.PriceMonthly, + Currency: instance.Currency, + RAM: instance.Ram, + }) + case storage.InstanceTypeMedium: + resp.InstanceTypes.Medium = append(resp.InstanceTypes.Medium, &models.DeploymentInstanceType{ + Code: instance.InstanceName, + CPU: instance.Cpu, + PriceHourly: instance.PriceHourly, + PriceMonthly: instance.PriceMonthly, + Currency: instance.Currency, + RAM: instance.Ram, + }) + case storage.InstanceTypeLarge: + resp.InstanceTypes.Large = append(resp.InstanceTypes.Large, &models.DeploymentInstanceType{ + Code: instance.InstanceName, + CPU: instance.Cpu, + PriceHourly: instance.PriceHourly, + PriceMonthly: instance.PriceMonthly, + Currency: instance.Currency, + RAM: instance.Ram, + }) + } + } + + for _, cloudVolume := range providerInfo.CloudVolumes { + resp.Volumes = append(resp.Volumes, &models.ResponseDeploymentInfoVolumesItems0{ + Currency: cloudVolume.Currency, + MaxSize: cloudVolume.VolumeMaxSize, + MinSize: cloudVolume.VolumeMinSize, + PriceMonthly: cloudVolume.PriceMonthly, + VolumeDescription: cloudVolume.VolumeDescription, + VolumeType: cloudVolume.VolumeType, + IsDefault: pointy.Bool(cloudVolume.IsDefault), + }) + } + + return resp +} + +func findCloudImage(images []storage.CloudImage, regionName string) *storage.CloudImage { + for i, image := range images { + if image.Region == regionName { + return &images[i] + } + } + + return nil +} diff --git a/console/service/internal/convert/operations.go b/console/service/internal/convert/operations.go new file mode 100644 index 000000000..8918d2221 --- /dev/null +++ b/console/service/internal/convert/operations.go @@ -0,0 +1,35 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func OperationViewToSwagger(op *storage.OperationView) *models.ResponseOperation { + return &models.ResponseOperation{ + ClusterName: op.Cluster, + Environment: op.Environment, + Finished: func() *strfmt.DateTime { + if op.Finished == nil { + return nil + } + finished := strfmt.DateTime(*op.Finished) + + return &finished + }(), + ID: op.ID, + Started: strfmt.DateTime(op.Started), + Status: op.Status, + Type: op.Type, + } +} + +func OperationsViewToSwagger(ops []storage.OperationView) []*models.ResponseOperation { + resp := make([]*models.ResponseOperation, 0, len(ops)) + for _, op := range ops { + resp = append(resp, OperationViewToSwagger(&op)) + } + + return resp +} diff --git a/console/service/internal/convert/postgres_versions.go b/console/service/internal/convert/postgres_versions.go new file mode 100644 index 000000000..edf9b4b26 --- /dev/null +++ b/console/service/internal/convert/postgres_versions.go @@ -0,0 +1,24 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func PostgresVersion(pv *storage.PostgresVersion) *models.ResponsePostgresVersion { + return &models.ResponsePostgresVersion{ + EndOfLife: strfmt.Date(pv.EndOfLife), + MajorVersion: pv.MajorVersion, + ReleaseDate: strfmt.Date(pv.ReleaseDate), + } +} + +func PostgresVersions(pvs []storage.PostgresVersion) []*models.ResponsePostgresVersion { + resp := make([]*models.ResponsePostgresVersion, 0, len(pvs)) + for _, pv := range pvs { + resp = append(resp, PostgresVersion(&pv)) + } + + return resp +} diff --git a/console/service/internal/convert/projects.go b/console/service/internal/convert/projects.go new file mode 100644 index 000000000..de3f0100c --- /dev/null +++ b/console/service/internal/convert/projects.go @@ -0,0 +1,33 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func ProjectToSwagger(prj *storage.Project) *models.ResponseProject { + return &models.ResponseProject{ + CreatedAt: strfmt.DateTime(prj.CreatedAt), + Description: prj.Description, + ID: prj.ID, + Name: prj.Name, + UpdatedAt: func() *strfmt.DateTime { + if prj.UpdatedAt == nil { + return nil + } + updated := strfmt.DateTime(*prj.UpdatedAt) + + return &updated + }(), + } +} + +func ProjectsToSwagger(projects []storage.Project) []*models.ResponseProject { + resp := make([]*models.ResponseProject, 0, len(projects)) + for _, prj := range projects { + resp = append(resp, ProjectToSwagger(&prj)) + } + + return resp +} diff --git a/console/service/internal/convert/secret.go b/console/service/internal/convert/secret.go new file mode 100644 index 000000000..a02108fcf --- /dev/null +++ b/console/service/internal/convert/secret.go @@ -0,0 +1,36 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func SecretViewToSwagger(secret *storage.SecretView) *models.ResponseSecretInfo { + return &models.ResponseSecretInfo{ + CreatedAt: strfmt.DateTime(secret.CreatedAt), + ID: secret.ID, + IsUsed: secret.IsUsed, + Name: secret.Name, + ProjectID: secret.ProjectID, + Type: models.SecretType(secret.Type), + UpdatedAt: func() *strfmt.DateTime { + if secret.UpdatedAt == nil { + return nil + } + updated := strfmt.DateTime(*secret.UpdatedAt) + + return &updated + }(), + UsedByClusters: secret.UsedByClusters, + } +} + +func SecretsViewToSwagger(secrets []storage.SecretView) []*models.ResponseSecretInfo { + resp := make([]*models.ResponseSecretInfo, 0, len(secrets)) + for _, sec := range secrets { + resp = append(resp, SecretViewToSwagger(&sec)) + } + + return resp +} diff --git a/console/service/internal/convert/settings.go b/console/service/internal/convert/settings.go new file mode 100644 index 000000000..32347fdeb --- /dev/null +++ b/console/service/internal/convert/settings.go @@ -0,0 +1,33 @@ +package convert + +import ( + "github.com/go-openapi/strfmt" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/models" +) + +func SettingToSwagger(s *storage.Setting) *models.ResponseSetting { + return &models.ResponseSetting{ + CreatedAt: strfmt.DateTime(s.CreatedAt), + ID: s.ID, + Name: s.Name, + UpdatedAt: func() *strfmt.DateTime { + if s.UpdatedAt == nil { + return nil + } + updated := strfmt.DateTime(*s.UpdatedAt) + + return &updated + }(), + Value: s.Value, + } +} + +func SettingsToSwagger(settings []storage.Setting) []*models.ResponseSetting { + resp := make([]*models.ResponseSetting, 0, len(settings)) + for _, s := range settings { + resp = append(resp, SettingToSwagger(&s)) + } + + return resp +} diff --git a/console/service/internal/db/db.go b/console/service/internal/db/db.go new file mode 100644 index 000000000..74f6c1678 --- /dev/null +++ b/console/service/internal/db/db.go @@ -0,0 +1,31 @@ +package db + +import ( + "context" + "fmt" + "postgesql-cluster-console/internal/configuration" + "time" + + "github.com/jackc/pgx/v5/pgxpool" +) + +func NewDbPool(cfg *configuration.Config) (*pgxpool.Pool, error) { + connString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s", + cfg.Db.User, cfg.Db.Password, cfg.Db.Host, cfg.Db.Port, cfg.Db.DbName) + poolConfig, err := pgxpool.ParseConfig(connString) + if err != nil { + return nil, err + } + //poolConfig.ConnConfig.PreferSimpleProtocol = true //(don't need simple protocol https://github.com/jackc/pgx/issues/650) + poolConfig.ConnConfig.Tracer = NewTracerZerolog() + poolConfig.MaxConns = cfg.Db.MaxConns + poolConfig.HealthCheckPeriod = time.Minute * 10 + if cfg.Db.MaxConnLifeTime != 0 { + poolConfig.MaxConnLifetime = cfg.Db.MaxConnLifeTime + } + if cfg.Db.MaxConnIdleTime != 0 { + poolConfig.MaxConnIdleTime = cfg.Db.MaxConnIdleTime + } + + return pgxpool.NewWithConfig(context.Background(), poolConfig) +} diff --git a/console/service/internal/db/tracer.go b/console/service/internal/db/tracer.go new file mode 100644 index 000000000..c095ee543 --- /dev/null +++ b/console/service/internal/db/tracer.go @@ -0,0 +1,124 @@ +package db + +import ( + "context" + "encoding/hex" + "fmt" + "postgesql-cluster-console/pkg/tracer" + "strings" + "time" + + "github.com/gdex-lab/go-render/render" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +type ( + traceCtxKey struct{} + traceCtxValue struct { + startTime time.Time + queryId string + } + tracerZerolog struct{} +) + +func NewTracerZerolog() pgx.QueryTracer { + return tracerZerolog{} +} + +func (t tracerZerolog) TraceQueryStart( + ctx context.Context, + conn *pgx.Conn, + data pgx.TraceQueryStartData, +) context.Context { + now := time.Now() + queryId := uuid.New().String() + localLog := t.makeTraceLogger(ctx, queryId) + + localLog.Debug().Str("sql", strings.Map(func(r rune) rune { + switch r { + case 0x000A, 0x0009, 0x000B, 0x000C, 0x000D, 0x0085, 0x2028, 0x2029: + return -1 + default: + return r + } + }, data.SQL)).Str("args", logQueryArgs(data.Args)).Msg("TraceQueryStart") + + return context.WithValue(ctx, traceCtxKey{}, &traceCtxValue{startTime: now, queryId: queryId}) +} + +func (t tracerZerolog) TraceQueryEnd( + ctx context.Context, + conn *pgx.Conn, + data pgx.TraceQueryEndData, +) { + traceValues, ok := ctx.Value(traceCtxKey{}).(*traceCtxValue) + if !ok { + return + } + + localLog := t.makeTraceLogger(ctx, traceValues.queryId) + msg := fmt.Sprintf("TraceQueryEnd duration: %s", time.Since(traceValues.startTime)) + if data.Err != nil { + localLog.Error().Err(data.Err).Msg(msg) + } else { + localLog.Debug().Msg(msg) + } +} + +func (t tracerZerolog) makeTraceLogger(ctx context.Context, queryId string) zerolog.Logger { + cid := getCid(ctx) + logCtx := log.With().Str("query_id", queryId) + if len(cid) != 0 { + logCtx = logCtx.Str("cid", cid) + } + + return logCtx.Logger() +} + +func getCid(ctx context.Context) string { + cid, ok := ctx.Value(tracer.CtxCidKey{}).(string) + if !ok { + return uuid.New().String() + } + + return cid +} + +func logQueryArgs(args []any) string { + //logArgs := make([]string, 0, len(args)) + + paramsStr := strings.Builder{} + paramsStr.WriteString("(") + + for i, a := range args { + switch v := a.(type) { + case []byte: + if len(v) < 64 { + a = hex.EncodeToString(v) + } else { + a = fmt.Sprintf("%x (truncated %d bytes)", v[:64], len(v)-64) + } + case string: + if len(v) > 64 { + a = fmt.Sprintf("%s (truncated %d bytes)", v[:64], len(v)-64) + } + } + if i != len(args)-1 { + paramsStr.WriteString(",") + } + + if stringer, ok := a.(fmt.Stringer); ok { + paramsStr.WriteString(stringer.String()) + } else { + paramsStr.WriteString(render.Render(a)) + } + } + + paramsStr.WriteString(")") + + return paramsStr.String() +} diff --git a/console/service/internal/service/service.go b/console/service/internal/service/service.go new file mode 100644 index 000000000..613e91359 --- /dev/null +++ b/console/service/internal/service/service.go @@ -0,0 +1,127 @@ +package service + +import ( + "github.com/go-openapi/runtime/middleware" + "postgesql-cluster-console/internal/configuration" + "postgesql-cluster-console/internal/controllers/cluster" + "postgesql-cluster-console/internal/controllers/dictionary" + "postgesql-cluster-console/internal/controllers/environment" + "postgesql-cluster-console/internal/controllers/operation" + "postgesql-cluster-console/internal/controllers/project" + "postgesql-cluster-console/internal/controllers/secret" + "postgesql-cluster-console/internal/controllers/setting" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/internal/watcher" + "postgesql-cluster-console/internal/xdocker" + "postgesql-cluster-console/models" + "postgesql-cluster-console/restapi" + "postgesql-cluster-console/restapi/operations" + "postgesql-cluster-console/restapi/operations/system" + + "github.com/go-openapi/loads" + "github.com/jessevdk/go-flags" + "github.com/rs/zerolog/log" +) + +type IService interface { + Serve() error +} + +type httpService struct { + srv *restapi.Server +} + +func NewService( + cfg *configuration.Config, + version string, + db storage.IStorage, + dockerManager xdocker.IManager, + logCollector watcher.LogCollector, + clusterWatcher watcher.ClusterWatcher, +) (IService, error) { + swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "2.0") + if err != nil { + return nil, err + } + api := operations.NewPgConsoleAPI(swaggerSpec) + srv := restapi.NewServer(api) + + srv.Host = cfg.Http.Host + srv.Port = cfg.Http.Port + srv.ReadTimeout = cfg.Http.ReadTimeout + srv.WriteTimeout = cfg.Http.WriteTimeout + restapi.Token = cfg.Authorization.Token + + localLog := log.With().Str("module", "http_server").Logger() + api.Logger = func(s string, i ...interface{}) { + localLog.Debug().Msgf(s, i...) + } + + if cfg.Https.IsUsed { + srv.EnabledListeners = append(srv.EnabledListeners, "https") + srv.TLSHost = cfg.Https.Host + srv.TLSPort = cfg.Https.Port + srv.TLSReadTimeout = cfg.Http.ReadTimeout + srv.TLSWriteTimeout = cfg.Http.WriteTimeout + srv.TLSCACertificate = flags.Filename(cfg.Https.CACert) + srv.TLSCertificate = flags.Filename(cfg.Https.ServerCert) + srv.TLSCertificateKey = flags.Filename(cfg.Https.ServerKey) + } + + api.DictionaryGetExternalDeploymentsHandler = dictionary.NewGetExternalDeploymentsHandler(db) + api.DictionaryGetDatabaseExtensionsHandler = dictionary.NewGetDbExtensionsHandler(db) + api.DictionaryGetPostgresVersionsHandler = dictionary.NewGetPostgresVersions(db) + + // environment + api.EnvironmentGetEnvironmentsHandler = environment.NewGetEnvironmentsHandler(db) + api.EnvironmentPostEnvironmentsHandler = environment.NewPostEnvironmentsHandler(db, log.Logger) + api.EnvironmentDeleteEnvironmentsIDHandler = environment.NewDeleteEnvironmentsHandler(db, log.Logger) + + // setting + api.SettingPostSettingsHandler = setting.NewPostSettingHandler(db) + api.SettingGetSettingsHandler = setting.NewGetSettingsHandler(db) + api.SettingPatchSettingsNameHandler = setting.NewPatchSettingHandler(db) + + // project + api.ProjectPostProjectsHandler = project.NewPostProjectHandler(db, log.Logger) + api.ProjectGetProjectsHandler = project.NewGetProjectsHandler(db) + api.ProjectDeleteProjectsIDHandler = project.NewDeleteProjectHandler(db, log.Logger) + api.ProjectPatchProjectsIDHandler = project.NewPatchProjectHandler(db) + + // secret + api.SecretPostSecretsHandler = secret.NewPostSecretHandler(db, log.Logger, cfg) + api.SecretGetSecretsHandler = secret.NewGetSecretHandler(db) + api.SecretDeleteSecretsIDHandler = secret.NewDeleteSecretHandler(db) + + // cluster + api.ClusterPostClustersHandler = cluster.NewPostClusterHandler(db, dockerManager, logCollector, cfg, log.Logger) + api.ClusterDeleteClustersIDHandler = cluster.NewDeleteClusterHandler(db) + api.OperationGetOperationsHandler = operation.NewGetOperationsHandler(db) + api.OperationGetOperationsIDLogHandler = operation.NewGetOperationLogHandler(db) + api.ClusterGetClustersHandler = cluster.NewGetClustersHandler(db, log.Logger) + api.ClusterGetClustersIDHandler = cluster.NewGetClusterHandler(db, log.Logger) + api.ClusterGetClustersDefaultNameHandler = cluster.NewGetClusterDefaultNameHandler(db, log.Logger) + api.ClusterPostClustersIDRemoveHandler = cluster.NewRemoveClusterHandler(db, dockerManager, logCollector, cfg, log.Logger) + api.ClusterDeleteServersIDHandler = cluster.NewDeleteServerHandler(db, log.Logger) + api.ClusterPostClustersIDRefreshHandler = cluster.NewPostClusterRefreshHandler(db, log.Logger, clusterWatcher) + + api.SystemGetVersionHandler = system.GetVersionHandlerFunc(func(params system.GetVersionParams) middleware.Responder { + return system.NewGetVersionOK().WithPayload(&models.ResponseVersion{ + Version: version, + }) + }) + + api.Logger = func(s string, i ...interface{}) { + log.Debug().Msgf(s, i...) + } + + srv.ConfigureAPI() + + return &httpService{ + srv: srv, + }, nil +} + +func (s *httpService) Serve() error { + return s.srv.Serve() +} diff --git a/console/service/internal/storage/cluster_flags.go b/console/service/internal/storage/cluster_flags.go new file mode 100644 index 000000000..d2e824b5a --- /dev/null +++ b/console/service/internal/storage/cluster_flags.go @@ -0,0 +1,16 @@ +package storage + +import "go.openly.dev/pointy" + +const ( + patroniConnectStatusMaskSet = uint32(0x1) + patroniConnectStatusMaskRemove = uint32(0xfffffff6) +) + +func SetPatroniConnectStatus(oldMask uint32, status uint32) *uint32 { + return pointy.Uint32((oldMask & patroniConnectStatusMaskRemove) | (status & patroniConnectStatusMaskSet)) +} + +func GetPatroniConnectStatus(mask uint32) uint32 { + return mask & patroniConnectStatusMaskSet +} diff --git a/console/service/internal/storage/cluster_flags_test.go b/console/service/internal/storage/cluster_flags_test.go new file mode 100644 index 000000000..f78075ec5 --- /dev/null +++ b/console/service/internal/storage/cluster_flags_test.go @@ -0,0 +1,13 @@ +package storage + +import ( + "gotest.tools/v3/assert" + "testing" +) + +func TestClusterFlags(t *testing.T) { + assert.Equal(t, uint32(1), *SetPatroniConnectStatus(0, 1)) + assert.Equal(t, uint32(1), *SetPatroniConnectStatus(1, 1)) + assert.Equal(t, uint32(0x11), *SetPatroniConnectStatus(0x10, 1)) + assert.Equal(t, uint32(0), *SetPatroniConnectStatus(1, 0)) +} diff --git a/console/service/internal/storage/consts.go b/console/service/internal/storage/consts.go new file mode 100644 index 000000000..7d0514a19 --- /dev/null +++ b/console/service/internal/storage/consts.go @@ -0,0 +1,55 @@ +package storage + +const ( + DefaultLimit = 20 + InstanceTypeSmall = "Small Size" + InstanceTypeMedium = "Medium Size" + InstanceTypeLarge = "Large Size" + + OperationStatusInProgress = "in_progress" + OperationStatusSuccess = "success" + OperationStatusFailed = "failed" + + OperationTypeDeploy = "deploy" + + ClusterStatusFailed = "failed" + ClusterStatusHealthy = "healthy" + ClusterStatusUnhealthy = "unhealthy" + ClusterStatusDegraded = "degraded" + ClusterStatusReady = "ready" + ClusterStatusUnavailable = "unavailable" +) + +var ( + secretSortFields = map[string]string{ + "name": "secret_name", + "id": "secret_id", + "type": "secret_type", + "created_at": "created_at", + "updated_at": "updated_at", + } + + clusterSortFields = map[string]string{ + "name": "cluster_name", + "id": "cluster_id", + "created_at": "created_at", + "updated_at": "updated_at", + "environment": "environment_id", + "status": "cluster_status", + "project": "project_id", + "location": "cluster_location", + "server_count": "server_count", + "postgres_version": "postgres_version", + } + + operationSortFields = map[string]string{ + "cluster_name": "cluster", + "type": "type", + "status": "status", + "id": "id", + "created_at": "created_at", + "updated_at": "updated_at", + "cluster": "cluster", + "environment": "environment", + } +) diff --git a/console/service/internal/storage/db_storage.go b/console/service/internal/storage/db_storage.go new file mode 100644 index 000000000..23dd6e25b --- /dev/null +++ b/console/service/internal/storage/db_storage.go @@ -0,0 +1,816 @@ +package storage + +import ( + "context" + "errors" + "strconv" + "time" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +type dbStorage struct { + db *pgxpool.Pool +} + +func NewDbStorage(db *pgxpool.Pool) IStorage { + return &dbStorage{ + db: db, + } +} + +func (s *dbStorage) GetCloudProviders(ctx context.Context, limit, offset *int64) ([]CloudProvider, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if limit != nil { + curLimit = *limit + } + if offset != nil { + curOffset = *offset + } + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from cloud_providers") + if err != nil { + return nil, nil, err + } + + cloudProviders, err := QueryRowsToStruct[CloudProvider](ctx, s.db, "select * from cloud_providers order by provider_name limit $1 offset $2", curLimit, curOffset) + if err != nil { + return nil, nil, err + } + + return cloudProviders, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) GetCloudProviderInfo(ctx context.Context, providerCode string) (*CloudProviderInfo, error) { + cloudInstances, err := QueryRowsToStruct[CloudInstance](ctx, s.db, "select * from cloud_instances where cloud_provider = $1 order by cpu, ram", providerCode) + if err != nil { + return nil, err + } + + cloudRegions, err := QueryRowsToStruct[CloudRegion](ctx, s.db, "select * from cloud_regions where cloud_provider = $1 order by region_name", providerCode) + if err != nil { + return nil, err + } + + cloudVolumes, err := QueryRowsToStruct[CloudVolume](ctx, s.db, "select * from cloud_volumes where cloud_provider = $1", providerCode) + if err != nil { + return nil, err + } + + cloudImages, err := QueryRowsToStruct[CloudImage](ctx, s.db, "select * from cloud_images where cloud_provider = $1", providerCode) + if err != nil { + return nil, err + } + + return &CloudProviderInfo{ + Code: providerCode, + CloudRegions: cloudRegions, + CloudInstances: cloudInstances, + CloudVolumes: cloudVolumes, + CloudImages: cloudImages, + }, nil +} + +func (s *dbStorage) GetPostgresVersions(ctx context.Context) ([]PostgresVersion, error) { + postgresVersions, err := QueryRowsToStruct[PostgresVersion](ctx, s.db, "select * from postgres_versions order by major_version") + if err != nil { + return nil, err + } + + return postgresVersions, nil +} + +func (s *dbStorage) CreateSetting(ctx context.Context, name string, value interface{}) (*Setting, error) { + setting, err := QueryRowToStruct[Setting](ctx, s.db, + `insert into settings(setting_name, setting_value) values($1, $2) returning *`, + name, value) + if err != nil { + return nil, err + } + + return setting, nil +} + +func (s *dbStorage) GetSettings(ctx context.Context, req *GetSettingsReq) ([]Setting, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if req.Limit != nil { + curLimit = *req.Limit + } + if req.Offset != nil { + curOffset = *req.Offset + } + + var ( + extraWhere string + extraArgsCurPosition = 1 + ) + extraArgs := []interface{}{} + { + if req.Name != nil { + extraWhere = " where setting_name = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.Name) + extraArgsCurPosition++ + } + } + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from settings"+extraWhere, extraArgs...) + if err != nil { + return nil, nil, err + } + + limit := " limit $" + strconv.Itoa(extraArgsCurPosition) + " offset $" + strconv.Itoa(extraArgsCurPosition+1) + extraArgs = append(extraArgs, curLimit, curOffset) + + settings, err := QueryRowsToStruct[Setting](ctx, s.db, "select * from settings "+extraWhere+" order by id"+limit, extraArgs...) + if err != nil { + return nil, nil, err + } + + return settings, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) GetSettingByName(ctx context.Context, name string) (*Setting, error) { + setting, err := QueryRowToStruct[Setting](ctx, s.db, "select * from settings where setting_name = $1", name) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + return nil, err + } + + return setting, nil +} + +func (s *dbStorage) UpdateSetting(ctx context.Context, name string, value interface{}) (*Setting, error) { + setting, err := QueryRowToStruct[Setting](ctx, s.db, + `update settings set + setting_value = $1 + where setting_name = $2 + returning *`, + value, name) + if err != nil { + return nil, err + } + + return setting, nil +} + +func (s *dbStorage) CreateProject(ctx context.Context, name, description string) (*Project, error) { + project, err := QueryRowToStruct[Project](ctx, s.db, + `insert into projects(project_name, project_description) values($1, $2) returning *`, + name, description) + if err != nil { + return nil, err + } + + return project, nil +} + +func (s *dbStorage) GetProjects(ctx context.Context, limit, offset *int64) ([]Project, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if limit != nil { + curLimit = *limit + } + if offset != nil { + curOffset = *offset + } + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from projects") + if err != nil { + return nil, nil, err + } + + projects, err := QueryRowsToStruct[Project](ctx, s.db, "select * from projects order by project_id limit $1 offset $2", curLimit, curOffset) + if err != nil { + return nil, nil, err + } + + return projects, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) GetProject(ctx context.Context, id int64) (*Project, error) { + project, err := QueryRowToStruct[Project](ctx, s.db, "select * from projects where project_id = $1", id) + if err != nil { + return nil, err + } + + return project, nil +} + +func (s *dbStorage) GetProjectByName(ctx context.Context, name string) (*Project, error) { + project, err := QueryRowToStruct[Project](ctx, s.db, "select * from projects where project_name = $1", name) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + return nil, err + } + + return project, nil +} + +func (s *dbStorage) DeleteProject(ctx context.Context, id int64) error { + _, err := s.db.Exec(ctx, "delete from projects where project_id=$1", id) + + return err +} + +func (s *dbStorage) UpdateProject(ctx context.Context, id int64, name, description *string) (*Project, error) { + project, err := QueryRowToStruct[Project](ctx, s.db, + `update projects set + project_name = coalesce($1, project_name), + project_description = coalesce($2, project_description) + where project_id = $3 + returning *`, + name, description, id) + if err != nil { + return nil, err + } + + return project, nil +} + +func (s *dbStorage) GetEnvironments(ctx context.Context, limit, offset *int64) ([]Environment, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if limit != nil { + curLimit = *limit + } + if offset != nil { + curOffset = *offset + } + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from environments") + if err != nil { + return nil, nil, err + } + + environments, err := QueryRowsToStruct[Environment](ctx, s.db, "select * from environments order by environment_id limit $1 offset $2", curLimit, curOffset) + if err != nil { + return nil, nil, err + } + + return environments, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) GetEnvironment(ctx context.Context, id int64) (*Environment, error) { + environment, err := QueryRowToStruct[Environment](ctx, s.db, "select * from environments where environment_id = $1", id) + if err != nil { + return nil, err + } + + return environment, nil +} + +func (s *dbStorage) GetEnvironmentByName(ctx context.Context, name string) (*Environment, error) { + environment, err := QueryRowToStruct[Environment](ctx, s.db, "select * from environments where environment_name = $1", name) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + return nil, err + } + + return environment, nil +} + +func (s *dbStorage) CreateEnvironment(ctx context.Context, req *AddEnvironmentReq) (*Environment, error) { + environment, err := QueryRowToStruct[Environment](ctx, s.db, "insert into environments(environment_name, environment_description) values($1, $2) returning *", + req.Name, req.Description) + if err != nil { + return nil, err + } + + return environment, nil +} + +func (s *dbStorage) DeleteEnvironment(ctx context.Context, id int64) error { + _, err := s.db.Exec(ctx, "delete from environments where environment_id=$1", id) + + return err +} + +func (s *dbStorage) CheckEnvironmentIsUsed(ctx context.Context, id int64) (bool, error) { + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from clusters where environment_id = $1", id) + if err != nil { + return false, err + } + + return count != 0, nil +} + +func (s *dbStorage) GetSecrets(ctx context.Context, req *GetSecretsReq) ([]SecretView, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if req.Limit != nil { + curLimit = *req.Limit + } + if req.Offset != nil { + curOffset = *req.Offset + } + + var ( + extraWhere string + extraArgsCurPosition = 2 + ) + extraArgs := []interface{}{req.ProjectID} + { + if req.Name != nil { + extraWhere = " and secret_name = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.Name) + extraArgsCurPosition++ + } + if req.Type != nil { + extraWhere += " and secret_type = $" + strconv.Itoa(extraArgsCurPosition) + extraArgsCurPosition++ + extraArgs = append(extraArgs, req.Type) + } + } + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from secrets where project_id = $1"+extraWhere, extraArgs...) + if err != nil { + return nil, nil, err + } + + orderBy := OrderByConverter(req.SortBy, "secret_id", secretSortFields) + + limit := " limit $" + strconv.Itoa(extraArgsCurPosition) + " offset $" + strconv.Itoa(extraArgsCurPosition+1) + extraArgs = append(extraArgs, curLimit, curOffset) + + secrets, err := QueryRowsToStruct[SecretView](ctx, s.db, "select * from v_secrets_list where project_id = $1 "+extraWhere+" order by "+orderBy+limit, extraArgs...) + if err != nil { + return nil, nil, err + } + + return secrets, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) GetSecret(ctx context.Context, id int64) (*SecretView, error) { + sec, err := QueryRowToStruct[SecretView](ctx, s.db, "select * from v_secrets_list where secret_id = $1", id) + if err != nil { + return nil, err + } + + return sec, nil +} + +func (s *dbStorage) GetSecretByName(ctx context.Context, name string) (*SecretView, error) { + sec, err := QueryRowToStruct[SecretView](ctx, s.db, "select * from v_secrets_list where secret_name = $1", name) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + return nil, err + } + + return sec, nil +} + +func (s *dbStorage) CreateSecret(ctx context.Context, req *AddSecretReq) (*SecretView, error) { + secretID, err := QueryRowToScalar[int64](ctx, s.db, "select * from add_secret($1, $2, $3, $4, $5)", + req.ProjectID, req.Type, req.Name, req.Value, req.SecretKey) + if err != nil { + return nil, err + } + + secret, err := QueryRowToStruct[SecretView](ctx, s.db, "select * from v_secrets_list where secret_id = $1 ", secretID) + if err != nil { + return nil, err + } + + return secret, err +} + +func (s *dbStorage) UpdateSecret(ctx context.Context, req *EditSecretReq) (*SecretView, error) { + return nil, nil +} + +func (s *dbStorage) DeleteSecret(ctx context.Context, id int64) error { + _, err := s.db.Exec(ctx, "delete from secrets where secret_id=$1", id) + + return err +} + +func (s *dbStorage) GetSecretVal(ctx context.Context, id int64, secretKey string) ([]byte, error) { + secretVal, err := QueryRowToScalar[[]byte](ctx, s.db, "select * from get_secret($1, $2)", + id, secretKey) + if err != nil { + return nil, err + } + + return secretVal, nil +} + +func (s *dbStorage) GetExtensions(ctx context.Context, req *GetExtensionsReq) ([]Extension, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if req.Limit != nil { + curLimit = *req.Limit + } + if req.Offset != nil { + curOffset = *req.Offset + } + + subQuery := ` WHERE (e.postgres_min_version IS NULL OR e.postgres_min_version::float <= $1) + AND (e.postgres_max_version IS NULL OR e.postgres_max_version::float >= $1) + AND ($2 = 'all' OR ($2 = 'contrib' AND e.contrib = true) OR ($2 = 'third_party' AND e.contrib = false))` + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from extensions as e "+subQuery, req.PostgresVersion, req.Type) + if err != nil { + return nil, nil, err + } + + extensions, err := QueryRowsToStruct[Extension](ctx, s.db, "select * from extensions as e"+subQuery+ + "ORDER BY e.contrib, e.extension_image IS NULL, e.extension_name limit $3 offset $4", + req.PostgresVersion, req.Type, curLimit, curOffset) + if err != nil { + return nil, nil, err + } + + return extensions, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) CreateCluster(ctx context.Context, req *CreateClusterReq) (*Cluster, error) { + cluster, err := QueryRowToStruct[Cluster](ctx, s.db, `insert into clusters(project_id, environment_id, cluster_name, cluster_description, secret_id, extra_vars, cluster_status, cluster_location, server_count, postgres_version, inventory) + values($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning *`, req.ProjectID, req.EnvironmentID, req.Name, req.Description, req.SecretID, req.ExtraVars, req.Status, req.Location, req.ServerCount, req.PostgreSqlVersion, req.Inventory) + if err != nil { + return nil, err + } + + return cluster, nil +} + +func (s *dbStorage) UpdateCluster(ctx context.Context, req *UpdateClusterReq) (*Cluster, error) { + cluster, err := QueryRowToStruct[Cluster](ctx, s.db, + `update clusters + set connection_info = coalesce($1, connection_info), + cluster_status = coalesce($2, cluster_status), + flags = coalesce($3, flags) + where cluster_id = $4 returning *`, + req.ConnectionInfo, req.Status, req.Flags, req.ID) + if err != nil { + return nil, err + } + + return cluster, nil +} + +func (s *dbStorage) GetDefaultClusterName(ctx context.Context) (string, error) { + name, err := QueryRowToScalar[string](ctx, s.db, "select * from get_cluster_name()") + if err != nil { + return "", err + } + + return name, nil +} + +func (s *dbStorage) CreateOperation(ctx context.Context, req *CreateOperationReq) (*Operation, error) { + operation, err := QueryRowToStruct[Operation](ctx, s.db, `insert into operations(project_id, cluster_id, docker_code, operation_type, operation_status, cid) + values($1, $2, $3, $4, $5, $6) returning *`, req.ProjectID, req.ClusterID, req.DockerCode, req.Type, OperationStatusInProgress, req.Cid) + if err != nil { + return nil, err + } + + return operation, nil +} + +func (s *dbStorage) GetClusterByName(ctx context.Context, name string) (*Cluster, error) { + cluster, err := QueryRowToStruct[Cluster](ctx, s.db, "select * from clusters where cluster_name = $1", name) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + return nil, err + } + + return cluster, nil +} + +func (s *dbStorage) GetCluster(ctx context.Context, id int64) (*Cluster, error) { + cluster, err := QueryRowToStruct[Cluster](ctx, s.db, "select * from clusters where cluster_id = $1", id) + if err != nil { + return nil, err + } + + return cluster, nil +} + +func (s *dbStorage) GetClusters(ctx context.Context, req *GetClustersReq) ([]Cluster, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if req.Limit != nil { + curLimit = *req.Limit + } + if req.Offset != nil { + curOffset = *req.Offset + } + + var ( + extraWhere string + extraArgsCurPosition = 2 + ) + extraArgs := []interface{}{req.ProjectID} + { + if req.Name != nil { + extraWhere += " and cluster_name = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.Name) + extraArgsCurPosition++ + } + if req.Status != nil { + extraWhere += " and cluster_status = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.Status) + extraArgsCurPosition++ + } + if req.Location != nil { + extraWhere += " and cluster_location = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.Location) + extraArgsCurPosition++ + } + if req.EnvironmentID != nil { + extraWhere += " and environment_id = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.EnvironmentID) + extraArgsCurPosition++ + } + if req.ServerCount != nil { + extraWhere += " and server_count = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.ServerCount) + extraArgsCurPosition++ + } + if req.PostgresVersion != nil { + extraWhere += " and postgres_version = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.PostgresVersion) + extraArgsCurPosition++ + } + if req.CreatedAtFrom != nil { + extraWhere += " and created_at >= $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.CreatedAtFrom) + extraArgsCurPosition++ + } + if req.CreatedAtTo != nil { + extraWhere += " and created_at <= $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.CreatedAtTo) + extraArgsCurPosition++ + } + } + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from clusters where project_id = $1 and deleted_at is null"+extraWhere, extraArgs...) + if err != nil { + return nil, nil, err + } + + orderBy := OrderByConverter(req.SortBy, "cluster_id", clusterSortFields) + + limit := " limit $" + strconv.Itoa(extraArgsCurPosition) + " offset $" + strconv.Itoa(extraArgsCurPosition+1) + extraArgs = append(extraArgs, curLimit, curOffset) + + clusters, err := QueryRowsToStruct[Cluster](ctx, s.db, "select * from clusters where project_id = $1 and deleted_at is null"+extraWhere+" order by "+orderBy+limit, + extraArgs...) + if err != nil { + return nil, nil, err + } + + return clusters, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) DeleteCluster(ctx context.Context, id int64) error { + _, err := s.db.Exec(ctx, "delete from operations where cluster_id = $1", id) + if err != nil { + return err + } + + _, err = s.db.Exec(ctx, "delete from servers where cluster_id = $1", id) + if err != nil { + return err + } + + _, err = s.db.Exec(ctx, "delete from clusters where cluster_id = $1", id) + if err != nil { + return err + } + + return nil +} + +func (s *dbStorage) DeleteClusterSoft(ctx context.Context, id int64) error { + _, err := s.db.Exec(ctx, "update clusters set deleted_at = CURRENT_TIMESTAMP, secret_id = null where cluster_id = $1", id) + + return err +} + +func (s *dbStorage) DeleteServer(ctx context.Context, id int64) error { + _, err := s.db.Exec(ctx, "delete from servers where server_id = $1", id) + if err != nil { + return err + } + + return nil +} + +func (s *dbStorage) GetInProgressOperations(ctx context.Context, from time.Time) ([]Operation, error) { + operations, err := QueryRowsToStruct[Operation](ctx, s.db, "select * from operations where operation_status = $1 and created_at > $2", + OperationStatusInProgress, from) + if err != nil { + return nil, err + } + + return operations, nil +} + +func (s *dbStorage) UpdateOperation(ctx context.Context, req *UpdateOperationReq) (*Operation, error) { + operation, err := QueryRowToStruct[Operation](ctx, s.db, + `update operations + set operation_status = coalesce($1, operation_status), + operation_log = case when $2::text is null then operation_log else concat(operation_log, CHR(10), $2::text) end + where id = $3 returning id, project_id, cluster_id, docker_code, cid, operation_type, operation_status, null, created_at, updated_at`, + req.Status, req.Logs, req.ID) + if err != nil { + return nil, err + } + + return operation, nil +} + +func (s *dbStorage) GetOperations(ctx context.Context, req *GetOperationsReq) ([]OperationView, *MetaPagination, error) { + var ( + curOffset = int64(0) + curLimit = int64(DefaultLimit) + ) + if req.Limit != nil { + curLimit = *req.Limit + } + if req.Offset != nil { + curOffset = *req.Offset + } + + subQuery := `WHERE project_id = $1 and started >= $2 and started <= $3` + + var ( + extraWhere string + extraArgsCurPosition = 4 + ) + extraArgs := []interface{}{req.ProjectID, req.StartedFrom, req.EndedTill} + { + if req.ClusterName != nil { + extraWhere = " and cluster = $" + strconv.Itoa(extraArgsCurPosition) + extraArgs = append(extraArgs, req.ClusterName) + extraArgsCurPosition++ + } + if req.Type != nil { + extraWhere += " and type = $" + strconv.Itoa(extraArgsCurPosition) + extraArgsCurPosition++ + extraArgs = append(extraArgs, req.Type) + } + if req.Status != nil { + extraWhere += " and status = $" + strconv.Itoa(extraArgsCurPosition) + extraArgsCurPosition++ + extraArgs = append(extraArgs, req.Status) + } + if req.Environment != nil { + extraWhere += " and environment = $" + strconv.Itoa(extraArgsCurPosition) + extraArgsCurPosition++ + extraArgs = append(extraArgs, req.Environment) + } + } + + count, err := QueryRowToScalar[int64](ctx, s.db, "select count(*) from v_operations "+subQuery+extraWhere, extraArgs...) + if err != nil { + return nil, nil, err + } + + orderBy := OrderByConverter(req.SortBy, "id DESC", operationSortFields) + + limit := " limit $" + strconv.Itoa(extraArgsCurPosition) + " offset $" + strconv.Itoa(extraArgsCurPosition+1) + extraArgs = append(extraArgs, curLimit, curOffset) + + operations, err := QueryRowsToStruct[OperationView](ctx, s.db, "select * from v_operations "+subQuery+extraWhere+ + " order by "+orderBy+limit, + extraArgs...) + if err != nil { + return nil, nil, err + } + + return operations, &MetaPagination{ + Limit: curLimit, + Offset: curOffset, + Count: count, + }, nil +} + +func (s *dbStorage) GetOperation(ctx context.Context, id int64) (*Operation, error) { + operation, err := QueryRowToStruct[Operation](ctx, s.db, "select * from operations where id = $1", id) + if err != nil { + return nil, err + } + + return operation, nil +} + +func (s *dbStorage) CreateServer(ctx context.Context, req *CreateServerReq) (*Server, error) { + server, err := QueryRowToStruct[Server](ctx, s.db, `insert into servers(cluster_id, server_name, server_location, ip_address) + values($1, $2, $3, $4) returning *`, req.ClusterID, req.ServerName, req.ServerLocation, req.IpAddress) + if err != nil { + return nil, err + } + + return server, nil +} + +func (s *dbStorage) GetServer(ctx context.Context, id int64) (*Server, error) { + server, err := QueryRowToStruct[Server](ctx, s.db, "select * from servers where server_id = $1", id) + if err != nil { + return nil, err + } + + return server, nil +} + +func (s *dbStorage) GetClusterServers(ctx context.Context, clusterID int64) ([]Server, error) { + servers, err := QueryRowsToStruct[Server](ctx, s.db, "select * from servers where cluster_id = $1", clusterID) + if err != nil { + return nil, err + } + + return servers, nil +} + +func (s *dbStorage) UpdateServer(ctx context.Context, req *UpdateServerReq) (*Server, error) { + server, err := QueryRowToStruct[Server](ctx, s.db, + `insert into servers(cluster_id, ip_address, server_name, server_role, server_status, timeline, lag, tags, pending_restart) + values($1, $2, $3, $4, $5, $6, $7, $8, $9) on conflict(cluster_id, ip_address) do update + set server_name = case when EXCLUDED.server_name = '' then servers.server_name else EXCLUDED.server_name end, + server_role = coalesce(EXCLUDED.server_role, servers.server_role), + server_status = coalesce(EXCLUDED.server_status, servers.server_status), + timeline = coalesce(EXCLUDED.timeline, servers.timeline), + lag = EXCLUDED.lag, + tags = coalesce(EXCLUDED.tags, servers.tags), + pending_restart = coalesce(EXCLUDED.pending_restart, servers.pending_restart) returning *`, + req.ClusterID, req.IpAddress, req.Name, req.Role, req.Status, req.Timeline, req.Lag, req.Tags, req.PendingRestart) + if err != nil { + return nil, err + } + + return server, nil +} + +func (s *dbStorage) ResetServer(ctx context.Context, clusterID int64, ipAddress string) (*Server, error) { + server, err := QueryRowToStruct[Server](ctx, s.db, + `update servers set + server_role = 'N/A', + server_status = 'N/A', + timeline = null, + lag = null, + tags = null where cluster_id = $1 and ip_address = $2 returning *`, + clusterID, ipAddress) + + if err != nil { + return nil, err + } + + return server, nil +} diff --git a/console/service/internal/storage/istorage.go b/console/service/internal/storage/istorage.go new file mode 100644 index 000000000..ed27adcc9 --- /dev/null +++ b/console/service/internal/storage/istorage.go @@ -0,0 +1,68 @@ +package storage + +import ( + "context" + "time" +) + +type IStorage interface { + GetCloudProviders(ctx context.Context, limit, offset *int64) ([]CloudProvider, *MetaPagination, error) + GetCloudProviderInfo(ctx context.Context, providerCode string) (*CloudProviderInfo, error) + GetExtensions(ctx context.Context, req *GetExtensionsReq) ([]Extension, *MetaPagination, error) + GetPostgresVersions(ctx context.Context) ([]PostgresVersion, error) + + // environment + GetEnvironments(ctx context.Context, limit, offset *int64) ([]Environment, *MetaPagination, error) + GetEnvironment(ctx context.Context, id int64) (*Environment, error) + GetEnvironmentByName(ctx context.Context, name string) (*Environment, error) + CreateEnvironment(ctx context.Context, req *AddEnvironmentReq) (*Environment, error) + DeleteEnvironment(ctx context.Context, id int64) error + CheckEnvironmentIsUsed(ctx context.Context, id int64) (bool, error) + + // setting + CreateSetting(ctx context.Context, name string, value interface{}) (*Setting, error) + GetSettings(ctx context.Context, req *GetSettingsReq) ([]Setting, *MetaPagination, error) + GetSettingByName(ctx context.Context, name string) (*Setting, error) + UpdateSetting(ctx context.Context, name string, value interface{}) (*Setting, error) + + // project + CreateProject(ctx context.Context, name, description string) (*Project, error) + GetProjects(ctx context.Context, limit, offset *int64) ([]Project, *MetaPagination, error) + GetProject(ctx context.Context, id int64) (*Project, error) + GetProjectByName(ctx context.Context, name string) (*Project, error) + DeleteProject(ctx context.Context, id int64) error + UpdateProject(ctx context.Context, id int64, name, description *string) (*Project, error) + + // secrets + GetSecrets(ctx context.Context, req *GetSecretsReq) ([]SecretView, *MetaPagination, error) + GetSecret(ctx context.Context, id int64) (*SecretView, error) + GetSecretByName(ctx context.Context, name string) (*SecretView, error) + CreateSecret(ctx context.Context, req *AddSecretReq) (*SecretView, error) + DeleteSecret(ctx context.Context, id int64) error + GetSecretVal(ctx context.Context, id int64, secretKey string) ([]byte, error) + + // cluster + CreateCluster(ctx context.Context, req *CreateClusterReq) (*Cluster, error) + GetCluster(ctx context.Context, id int64) (*Cluster, error) + GetClusters(ctx context.Context, req *GetClustersReq) ([]Cluster, *MetaPagination, error) + GetDefaultClusterName(ctx context.Context) (string, error) + DeleteCluster(ctx context.Context, id int64) error + DeleteClusterSoft(ctx context.Context, id int64) error + DeleteServer(ctx context.Context, id int64) error + GetClusterByName(ctx context.Context, name string) (*Cluster, error) + UpdateCluster(ctx context.Context, req *UpdateClusterReq) (*Cluster, error) + + // operation + CreateOperation(ctx context.Context, req *CreateOperationReq) (*Operation, error) + GetOperations(ctx context.Context, req *GetOperationsReq) ([]OperationView, *MetaPagination, error) + GetOperation(ctx context.Context, id int64) (*Operation, error) + UpdateOperation(ctx context.Context, req *UpdateOperationReq) (*Operation, error) + GetInProgressOperations(ctx context.Context, from time.Time) ([]Operation, error) + + // server + CreateServer(ctx context.Context, req *CreateServerReq) (*Server, error) + GetServer(ctx context.Context, id int64) (*Server, error) + GetClusterServers(ctx context.Context, clusterID int64) ([]Server, error) + UpdateServer(ctx context.Context, req *UpdateServerReq) (*Server, error) + ResetServer(ctx context.Context, clusterID int64, ipAddress string) (*Server, error) +} diff --git a/console/service/internal/storage/models.go b/console/service/internal/storage/models.go new file mode 100644 index 000000000..be8e0673f --- /dev/null +++ b/console/service/internal/storage/models.go @@ -0,0 +1,311 @@ +package storage + +import ( + "net" + "time" +) + +type CloudProvider struct { + Code string + Description string + ProviderImage string +} + +type CloudRegion struct { + ProviderCode string + RegionGroup string + RegionName string + Description string +} + +type CloudInstance struct { + ProviderCode string + InstanceGroup string + InstanceName string + Arch string + Cpu int64 + Ram int64 + PriceHourly float64 + PriceMonthly float64 + Currency string + UpdatedAt time.Time +} + +type CloudImage struct { + ProviderCode string + Region string + Image interface{} + Arch string + OsName string + OsVersion string + UpdatedAt time.Time +} + +type CloudVolume struct { + ProviderCode string + VolumeType string + VolumeDescription string + VolumeMinSize int64 + VolumeMaxSize int64 + PriceMonthly float64 + Currency string + IsDefault bool + UpdatedAt time.Time +} + +type CloudProviderInfo struct { + Code string + CloudRegions []CloudRegion + CloudInstances []CloudInstance + CloudVolumes []CloudVolume + CloudImages []CloudImage +} + +type PostgresVersion struct { + MajorVersion int64 + ReleaseDate time.Time + EndOfLife time.Time +} + +type Setting struct { + ID int64 + Name string + Value interface{} + CreatedAt time.Time + UpdatedAt *time.Time +} + +type GetSettingsReq struct { + Name *string + + Limit *int64 + Offset *int64 +} + +type MetaPagination struct { + Limit int64 + Offset int64 + Count int64 +} + +type Project struct { + ID int64 + Name string + Description *string + CreatedAt time.Time + UpdatedAt *time.Time +} + +type Environment struct { + ID int64 + Name string + Description *string + CreatedAt time.Time + UpdatedAt *time.Time +} + +type AddEnvironmentReq struct { + Name string + Description string +} + +type SecretView struct { + ProjectID int64 + ID int64 + Name string + Type string + CreatedAt time.Time + UpdatedAt *time.Time + IsUsed bool + UsedByClusters *string +} + +type GetSecretsReq struct { + ProjectID int64 + Name *string + Type *string + SortBy *string + + Limit *int64 + Offset *int64 +} + +type AddSecretReq struct { + ProjectID int64 + Type string + Name string + Value []byte + SecretKey string +} + +type EditSecretReq struct { + ProjectID int64 + Type *string + Name *string + Value []byte + SecretKey string +} + +type Extension struct { + Name string + Description *string + Url *string + Image *string + PostgresMinVersion *string + PostgresMaxVersion *string + Contrib bool +} + +type GetExtensionsReq struct { + Type *string + PostgresVersion *string + + Limit *int64 + Offset *int64 +} + +type Cluster struct { + ID int64 + ProjectID int64 + EnvironmentID int64 + SecretID *int64 + Name string + Status string + Description string + Location *string + ConnectionInfo interface{} + ExtraVars []byte + Inventory []byte + ServersCount int32 + PostgreVersion int32 + CreatedAt time.Time + UpdatedAt *time.Time + DeletedAt *time.Time + Flags uint32 +} + +type GetClustersReq struct { + ProjectID int64 + Name *string + SortBy *string + Status *string + Location *string + ServerCount *int64 + PostgresVersion *int64 + EnvironmentID *int64 + CreatedAtFrom *time.Time + CreatedAtTo *time.Time + + Limit *int64 + Offset *int64 +} + +type CreateClusterReq struct { + ProjectID int64 + EnvironmentID int64 + Name string + Description string + SecretID *int64 + ExtraVars []string + Location string + ServerCount int + PostgreSqlVersion int + Status string + Inventory []byte +} + +type UpdateClusterReq struct { + ID int64 + ConnectionInfo interface{} + Status *string + Flags *uint32 +} + +type Operation struct { + ID int64 + ProjectID int64 + ClusterID int64 + DockerCode string + Cid string + Type string + Status string + Log *string + CreatedAt time.Time + UpdatedAt *time.Time +} + +type OperationView struct { + ProjectID int64 + ClusterID int64 + ID int64 + Started time.Time + Finished *time.Time + Type string + Status string + Cluster string + Environment string +} + +type CreateOperationReq struct { + ProjectID int64 + ClusterID int64 + DockerCode string + Type string + Cid string +} + +type UpdateOperationReq struct { + ID int64 + Status *string + Logs *string +} + +type GetOperationsReq struct { + ProjectID int64 + StartedFrom time.Time + EndedTill time.Time + ClusterName *string + Type *string + Status *string + Environment *string + SortBy *string + + Limit *int64 + Offset *int64 +} + +type Server struct { + ID int64 + ClusterID int64 + Name string + Location *string + Role string + Status string + IpAddress net.IP + Timeline *int64 + Lag *int64 + Tags interface{} + PendingRestart *bool + CreatedAt time.Time + UpdatedAt *time.Time +} + +type CreateServerReq struct { + ClusterID int64 + ServerName string + ServerLocation *string + IpAddress string +} + +type UpdateServerReq struct { + ClusterID int64 + IpAddress string + + Name string + Role *string + Status *string + Timeline *int64 + Lag *int64 + Tags interface{} + PendingRestart *bool +} diff --git a/console/service/internal/storage/utils.go b/console/service/internal/storage/utils.go new file mode 100644 index 000000000..36796700b --- /dev/null +++ b/console/service/internal/storage/utils.go @@ -0,0 +1,114 @@ +package storage + +import ( + "context" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "strings" +) + +func QueryRowsToStruct[output any]( + ctx context.Context, + pool *pgxpool.Pool, + query string, + args ...any, +) ([]output, error) { + rows, err := pool.Query(ctx, query, args...) + if err != nil { + return nil, err + } + + return pgx.CollectRows(rows, pgx.RowToStructByPos[output]) +} + +func QueryRowsToAddrStruct[output any]( + ctx context.Context, + pool *pgxpool.Pool, + query string, + args ...any, +) ([]*output, error) { + rows, err := pool.Query(ctx, query, args...) + if err != nil { + return nil, err + } + + return pgx.CollectRows(rows, pgx.RowToAddrOfStructByPos[output]) +} + +func QueryRowToStruct[output any]( + ctx context.Context, + pool *pgxpool.Pool, + query string, + args ...any, +) (*output, error) { + rows, err := pool.Query(ctx, query, args...) + if err != nil { + return nil, err + } + + var res output + res, err = pgx.CollectOneRow(rows, pgx.RowToStructByPos[output]) + if err != nil { + return nil, err + } + + return &res, nil +} + +func QueryRowToScalar[scalar any]( + ctx context.Context, + pool *pgxpool.Pool, + query string, + args ...any, +) (scalar, error) { + rows, err := pool.Query(ctx, query, args...) + if err != nil { + var value scalar + return value, err + } + + return pgx.CollectOneRow(rows, pgx.RowTo[scalar]) +} + +func QueryRowToScalarAddr[scalar any]( + ctx context.Context, + pool *pgxpool.Pool, + query string, + args ...any, +) (*scalar, error) { + rows, err := pool.Query(ctx, query, args...) + if err != nil { + return nil, err + } + + return pgx.CollectOneRow(rows, pgx.RowToAddrOf[scalar]) +} + +func OrderByConverter(sortByFromApi *string, defaultField string, convMap map[string]string) string { + orderBy := strings.Builder{} + if sortByFromApi != nil { + sortByFields := strings.Split(*sortByFromApi, ",") + for _, sortBy := range sortByFields { + if len(sortBy) == 0 { + continue + } + order := "ASC" + if sortBy[0] == '-' { + order = "DESC" + sortBy = sortBy[1:] + } + tableField := convMap[sortBy] + if len(tableField) != 0 { + if orderBy.Len() != 0 { + orderBy.WriteString(",") + } + orderBy.WriteString(tableField + " " + order) + } + } + } + if orderBy.Len() == 0 { + return defaultField + } + + return orderBy.String() +} diff --git a/console/service/internal/watcher/cluster_watcher.go b/console/service/internal/watcher/cluster_watcher.go new file mode 100644 index 000000000..7c21f6dd3 --- /dev/null +++ b/console/service/internal/watcher/cluster_watcher.go @@ -0,0 +1,291 @@ +package watcher + +import ( + "context" + "github.com/google/uuid" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "go.openly.dev/pointy" + "golang.org/x/sync/semaphore" + "postgesql-cluster-console/internal/configuration" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/pkg/patroni" + "postgesql-cluster-console/pkg/tracer" + "sync" + "time" +) + +type ClusterWatcher interface { + Run() + Stop() + HandleCluster(ctx context.Context, cl *storage.Cluster) +} + +type clusterWatcher struct { + db storage.IStorage + isRun bool + log zerolog.Logger + cfg *configuration.Config + patroniCli patroni.IClient + + ctx context.Context + done context.CancelFunc + wg sync.WaitGroup +} + +func NewServerWatcher(db storage.IStorage, patroniCli patroni.IClient, cfg *configuration.Config) ClusterWatcher { + return &clusterWatcher{ + db: db, + cfg: cfg, + patroniCli: patroniCli, + log: log.Logger.With().Str("module", "cluster_watcher").Logger(), + } +} + +func (sw *clusterWatcher) Run() { + if sw.isRun { + return + } + sw.isRun = true + + sw.ctx, sw.done = context.WithCancel(context.Background()) + sw.wg.Add(1) + go func() { + sw.loop() + sw.wg.Done() + }() + sw.log.Info().Msg("run") +} + +func (sw *clusterWatcher) Stop() { + sw.log.Info().Msg("stopping") + sw.done() + sw.wg.Wait() + sw.isRun = false + sw.log.Info().Msg("stopped") +} + +func (sw *clusterWatcher) loop() { + timer := time.NewTimer(sw.cfg.ClusterWatcher.RunEvery) + defer timer.Stop() + + for { + select { + case <-sw.ctx.Done(): + sw.log.Info().Msg("loop is done") + + return + case <-timer.C: + sw.doWork() + timer.Reset(sw.cfg.ClusterWatcher.RunEvery) + } + } +} + +func (sw *clusterWatcher) doWork() { + sw.log.Trace().Msg("doWork started") + defer sw.log.Trace().Msg("doWork was done") + ctx := context.WithValue(sw.ctx, tracer.CtxCidKey{}, uuid.New().String()) + projects, _, err := sw.db.GetProjects(ctx, pointy.Int64(1000), pointy.Int64(0)) + if err != nil { + sw.log.Error().Err(err).Msg("failed to get projects") + + return + } + sem := semaphore.NewWeighted(sw.cfg.ClusterWatcher.PoolSize) + for _, pr := range projects { + sw.handleProject(ctx, &pr, sem) + } + _ = sem.Acquire(ctx, sw.cfg.ClusterWatcher.PoolSize) // wait all workers done +} + +func (sw *clusterWatcher) handleProject(ctx context.Context, pr *storage.Project, sem *semaphore.Weighted) { + localLog := sw.log.With().Str("project", pr.Name).Logger() + localLog.Trace().Msg("started to handler project") + defer log.Trace().Msg("project was handled") + + var ( + offset = int64(0) + limit = int64(100) // handle by 100 clusters per call + ) + for { + if ctx.Err() != nil { + return + } + + clusters, _, err := sw.db.GetClusters(ctx, &storage.GetClustersReq{ + ProjectID: pr.ID, + Limit: &limit, + Offset: &offset, + }) + if err != nil { + localLog.Error().Err(err).Msg("failed to get clusters") + + continue + } + if len(clusters) == 0 { + localLog.Trace().Msg("all clusters were handled") + + return + } + + for _, cl := range clusters { + err = sem.Acquire(ctx, 1) + if err != nil { + localLog.Error().Err(err).Msg("failed to acquire semaphore") + + return + } + cl := cl // copy for async handling + go func() { + sw.HandleCluster(ctx, &cl) + sem.Release(1) + }() + } + offset += limit + } +} + +func (sw *clusterWatcher) HandleCluster(ctx context.Context, cl *storage.Cluster) { + localLog := sw.log.With().Str("cluster", cl.Name).Logger() + cid, ok := ctx.Value(tracer.CtxCidKey{}).(string) + if ok { + localLog.With().Str("cid", cid).Logger() + } + localLog.Trace().Msg("started to handle cluster") + defer localLog.Trace().Msg("cluster was handled") + + servers, err := sw.db.GetClusterServers(ctx, cl.ID) + if err != nil { + localLog.Error().Err(err).Msg("failed to get servers by cluster") + + return + } + + sw.handleClusterServers(ctx, cl, servers) +} + +func (sw *clusterWatcher) handleClusterServers(ctx context.Context, cl *storage.Cluster, clusterServers []storage.Server) { + localLog := sw.log.With().Str("cluster", cl.Name).Logger() + cid, ok := ctx.Value(tracer.CtxCidKey{}).(string) + if ok { + localLog.With().Str("cid", cid).Logger() + } + localLog.Trace().Msg("started to handle cluster servers") + defer localLog.Trace().Msg("cluster servers were handled") + + // map with old cluster topology + serversMap := make(map[string]bool) + for _, s := range clusterServers { + serversMap[s.IpAddress.String()] = false + } + + patroniHealthCheck := false + for _, s := range clusterServers { + if ctx.Err() != nil { + return + } + + clusterInfo, err := sw.patroniCli.GetClusterInfo(ctx, s.IpAddress.String()) + if err != nil { + localLog.Debug().Err(err).Msg("failed to get patroni info") + + continue + } + localLog.Trace().Any("cluster_info", &clusterInfo).Msg("got cluster info") + patroniHealthCheck = true + + const ( + stateRunning = "running" + stateStreaming = "streaming" + ) + healthyServers := int32(0) + + for _, serverInfo := range clusterInfo.Members { + var lag *int64 + switch l := serverInfo.Lag.(type) { + case int64: + lag = &l + case uint64: + lag = pointy.Int64(int64(l)) + case int8: + lag = pointy.Int64(int64(l)) + case uint8: + lag = pointy.Int64(int64(l)) + case int16: + lag = pointy.Int64(int64(l)) + case uint16: + lag = pointy.Int64(int64(l)) + case int: + lag = pointy.Int64(int64(l)) + case uint: + lag = pointy.Int64(int64(l)) + case int32: + lag = pointy.Int64(int64(l)) + case uint32: + lag = pointy.Int64(int64(l)) + case float64: + lag = pointy.Int64(int64(l)) + default: + localLog.Trace().Type("lag_type", l).Msg("unknown lag type") + } + updatedServer, err := sw.db.UpdateServer(ctx, &storage.UpdateServerReq{ + ClusterID: cl.ID, + IpAddress: serverInfo.Host, + Name: serverInfo.Name, + Role: &serverInfo.Role, + Status: &serverInfo.State, + Timeline: &serverInfo.Timeline, + Lag: lag, + Tags: &serverInfo.Tags, + PendingRestart: &serverInfo.PendingRestart, + }) + if err != nil { + localLog.Error().Err(err).Msg("failed to update server") + } else { + localLog.Trace().Any("server", updatedServer).Msg("server was updated") + serversMap[serverInfo.Host] = true + } + if serverInfo.State == stateRunning || serverInfo.State == stateStreaming { + healthyServers++ + } + } + var status string + if len(clusterInfo.Members) < int(cl.ServersCount) { + status = storage.ClusterStatusDegraded + } else if healthyServers < cl.ServersCount { + status = storage.ClusterStatusUnhealthy + } else { + status = storage.ClusterStatusHealthy + } + _, err = sw.db.UpdateCluster(ctx, &storage.UpdateClusterReq{ + ID: cl.ID, + Status: &status, + Flags: storage.SetPatroniConnectStatus(cl.Flags, 1), + }) + if err != nil { + localLog.Error().Err(err).Msg("failed to update cluster status") + } + break + } + if !patroniHealthCheck && storage.GetPatroniConnectStatus(cl.Flags) == 1 { + _, err := sw.db.UpdateCluster(ctx, &storage.UpdateClusterReq{ + ID: cl.ID, + Status: pointy.String(storage.ClusterStatusUnavailable), + }) + if err != nil { + localLog.Error().Err(err).Msg("failed to update cluster status") + } + } + + for ipAddress, updated := range serversMap { + if !updated { + updatedServer, err := sw.db.ResetServer(ctx, cl.ID, ipAddress) + if err != nil { + localLog.Error().Err(err).Msg("failed to update unknown server") + } else { + localLog.Trace().Any("server", updatedServer).Msg("unknown server was updated") + } + } + } +} diff --git a/console/service/internal/watcher/consts.go b/console/service/internal/watcher/consts.go new file mode 100644 index 000000000..10f7b03d8 --- /dev/null +++ b/console/service/internal/watcher/consts.go @@ -0,0 +1,10 @@ +package watcher + +const ( + ContainerStatusExited = "exited" + ContainerStatusRemoving = "removing" + ContainerStatusDead = "dead" + + LogFieldSystemInfo = "System info" + LogFieldConnectionInfo = "deploy-finish : Connection info" +) diff --git a/console/service/internal/watcher/log_collector.go b/console/service/internal/watcher/log_collector.go new file mode 100644 index 000000000..094ea81aa --- /dev/null +++ b/console/service/internal/watcher/log_collector.go @@ -0,0 +1,93 @@ +package watcher + +import ( + "context" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/internal/xdocker" + "postgesql-cluster-console/pkg/tracer" + "sync" +) + +type LogCollector interface { + StoreInDb(operationID int64, dockerCode xdocker.InstanceID, cid string) + PrintToConsole(dockerCode xdocker.InstanceID, cid string) + Stop() +} + +type logCollector struct { + db storage.IStorage + dockerManager xdocker.IManager + isRun bool + log zerolog.Logger + + ctx context.Context + done context.CancelFunc + wg sync.WaitGroup +} + +func NewLogCollector(db storage.IStorage, dockerManager xdocker.IManager) LogCollector { + lc := &logCollector{ + db: db, + dockerManager: dockerManager, + log: log.Logger.With().Str("module", "log_collector").Logger(), + } + lc.ctx, lc.done = context.WithCancel(context.Background()) + + return lc +} + +func (lc *logCollector) StoreInDb(operationID int64, dockerCode xdocker.InstanceID, cid string) { + lc.wg.Add(1) + go func() { + lc.log.Debug().Str("cid", cid).Int64("operation_id", operationID).Msg("log collector started") + lc.storeLogsFromContainer(operationID, dockerCode, cid) + defer func() { + lc.wg.Done() + lc.log.Debug().Str("cid", cid).Int64("operation_id", operationID).Msg("finished") + }() + }() +} + +func (lc *logCollector) PrintToConsole(dockerCode xdocker.InstanceID, cid string) { + lc.wg.Add(1) + go func() { + lc.log.Debug().Str("cid", cid).Msg("log collector started") + lc.printLogsFromContainer(dockerCode, cid) + defer func() { + lc.wg.Done() + lc.log.Debug().Str("cid", cid).Msg("finished") + }() + }() +} + +func (lc *logCollector) Stop() { + lc.log.Info().Msg("stopping") + lc.done() + lc.wg.Wait() + lc.log.Info().Msg("stopped") +} + +func (lc *logCollector) storeLogsFromContainer(operationID int64, dockerCode xdocker.InstanceID, cid string) { + ctx := context.WithValue(lc.ctx, tracer.CtxCidKey{}, cid) + lc.log.Trace().Msg("storeLogsFromContainer called") + lc.dockerManager.StoreContainerLogs(ctx, dockerCode, func(logMessage string) { + lc.log.Trace().Str("cid", cid).Str("proc", "storeLogsFromContainer").Msg(logMessage) + _, err := lc.db.UpdateOperation(ctx, &storage.UpdateOperationReq{ + ID: operationID, + Logs: &logMessage, + }) + if err != nil { + lc.log.Error().Err(err).Int64("operation_id", operationID).Msg("failed to update log") + } + }) +} + +func (lc *logCollector) printLogsFromContainer(dockerCode xdocker.InstanceID, cid string) { + ctx := context.WithValue(lc.ctx, tracer.CtxCidKey{}, cid) + lc.log.Trace().Msg("storeLogsFromContainer called") + lc.dockerManager.StoreContainerLogs(ctx, dockerCode, func(logMessage string) { + lc.log.Trace().Str("cid", cid).Msg(logMessage) + }) +} diff --git a/console/service/internal/watcher/log_watcher.go b/console/service/internal/watcher/log_watcher.go new file mode 100644 index 000000000..3bc8bb2fb --- /dev/null +++ b/console/service/internal/watcher/log_watcher.go @@ -0,0 +1,209 @@ +package watcher + +import ( + "context" + "encoding/json" + "github.com/mitchellh/mapstructure" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "os" + "postgesql-cluster-console/internal/configuration" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/internal/xdocker" + "postgesql-cluster-console/pkg/tracer" + "sync" + "time" +) + +type LogWatcher interface { + Run() + Stop() +} + +type logWatcher struct { + db storage.IStorage + dockerManager xdocker.IManager + isRun bool + log zerolog.Logger + cfg *configuration.Config + + ctx context.Context + done context.CancelFunc + wg sync.WaitGroup +} + +func NewLogWatcher(db storage.IStorage, dockerManager xdocker.IManager, cfg *configuration.Config) LogWatcher { + return &logWatcher{ + db: db, + dockerManager: dockerManager, + cfg: cfg, + log: log.Logger.With().Str("module", "log_watcher").Logger(), + } +} + +func (lw *logWatcher) Run() { + if lw.isRun { + return + } + lw.isRun = true + + lw.ctx, lw.done = context.WithCancel(context.Background()) + lw.wg.Add(1) + go func() { + lw.loop() + lw.wg.Done() + }() + lw.log.Info().Msg("run") +} + +func (lw *logWatcher) Stop() { + lw.log.Info().Msg("stopping") + lw.done() + lw.wg.Wait() + lw.isRun = false + lw.log.Info().Msg("stopped") +} + +func (lw *logWatcher) loop() { + timer := time.NewTimer(lw.cfg.LogWatcher.RunEvery) + defer timer.Stop() + + for { + select { + case <-lw.ctx.Done(): + lw.log.Info().Msg("loop is done") + + return + case <-timer.C: + lw.doWork() + timer.Reset(lw.cfg.LogWatcher.RunEvery) + } + } +} + +func (lw *logWatcher) doWork() { + lw.log.Debug().Msg("starting to collect info about operations performed on clusters") + operations, err := lw.db.GetInProgressOperations(lw.ctx, time.Now().Add(-lw.cfg.LogWatcher.AnalyzePast)) + if err != nil { + lw.log.Error().Err(err).Msg("failed to get in_progress operations") + + return + } + for _, op := range operations { + localLog := lw.log.With().Str("cid", op.Cid).Int64("operation_id", op.ID).Logger() + localLog.Trace().Msg("starting to collect info") + + opCtx := context.WithValue(lw.ctx, tracer.CtxCidKey{}, op.Cid) + containerStatus, err := lw.dockerManager.GetStatus(opCtx, xdocker.InstanceID(op.DockerCode)) + if err != nil { + localLog.Error().Err(err).Msg("failed to get containers status") + continue + } + localLog.Trace().Str("container_status", containerStatus).Msg("got container status") + switch containerStatus { + case ContainerStatusExited, ContainerStatusDead, ContainerStatusRemoving: + lw.collectContainerLog(opCtx, &op, localLog) + err = lw.dockerManager.RemoveContainer(opCtx, xdocker.InstanceID(op.DockerCode)) + if err != nil { + localLog.Error().Err(err).Msg("failed to remove container") + } + default: + localLog.Trace().Msg("skipped") + } + } +} + +func (lw *logWatcher) collectContainerLog(ctx context.Context, op *storage.Operation, log zerolog.Logger) { + clusterInfo, err := lw.db.GetCluster(ctx, op.ID) + if err != nil { + log.Error().Err(err).Msg("failed to get cluster from db") + + return + } + + fileLog := lw.cfg.Docker.LogDir + "/" + clusterInfo.Name + ".json" + fLog, err := os.Open(fileLog) + if err != nil { + log.Error().Err(err).Str("file_name", fileLog).Msg("can't open file with log") + + return + } + + var logs []LogEntity + jsonDec := json.NewDecoder(fLog) + err = jsonDec.Decode(&logs) + if err != nil { + log.Error().Err(err).Msg("failed to decode file log") + + return + } + + var status string + for _, logEntity := range logs { + switch logEntity.Task { + case LogFieldSystemInfo: + var serverInfo SystemInfo + err = mapstructure.Decode(logEntity.Msg, &serverInfo) + if err != nil { + log.Error().Err(err).Any("msg", logEntity.Msg).Msg("failed to decode system_info") + continue + } + + createdServer, err := lw.db.CreateServer(ctx, &storage.CreateServerReq{ + ClusterID: clusterInfo.ID, + ServerName: serverInfo.ServerName, + ServerLocation: serverInfo.ServerLocation, + IpAddress: serverInfo.IpAddress, + }) + if err != nil { + log.Error().Err(err).Msg("failed to store server to db") + + continue + } + log.Trace().Any("server", createdServer).Msg("server was created") + case LogFieldConnectionInfo: + _, err := lw.db.UpdateCluster(ctx, &storage.UpdateClusterReq{ + ID: op.ClusterID, + ConnectionInfo: logEntity.Msg, + }) + if err != nil { + log.Error().Err(err).Msg("failed to update cluster") + + continue + } + } + if logEntity.Summary != nil { + status = logEntity.Status + } + } + if len(status) == 0 { + log.Warn().Msg("summary not found in logs") + + status = storage.OperationStatusFailed + } + updatedOperation, err := lw.db.UpdateOperation(ctx, &storage.UpdateOperationReq{ + ID: op.ID, + Status: &status, + }) + if err != nil { + log.Error().Err(err).Msg("failed to update operation status in db") + } else { + log.Trace().Any("operation", updatedOperation).Msg("operation was updated in db") + } + + // set cluster status + if status == storage.OperationStatusFailed { + status = storage.ClusterStatusFailed + } else { + status = storage.ClusterStatusReady + } + updatedCluster, err := lw.db.UpdateCluster(ctx, &storage.UpdateClusterReq{ + ID: op.ClusterID, + Status: &status, + }) + if err != nil { + log.Error().Err(err).Msg("failed to update cluster status in db") + } else { + log.Trace().Any("cluster", updatedCluster).Msg("cluster was updated in db") + } +} diff --git a/console/service/internal/watcher/models.go b/console/service/internal/watcher/models.go new file mode 100644 index 000000000..e20cce630 --- /dev/null +++ b/console/service/internal/watcher/models.go @@ -0,0 +1,15 @@ +package watcher + +type LogEntity struct { + Task string `json:"task"` + Failed bool `json:"failed"` + Msg interface{} `json:"msg"` + Summary interface{} `json:"summary,omitempty"` + Status string `json:"status"` +} + +type SystemInfo struct { + ServerLocation *string `json:"server_location,omitempty" mapstructure:"server_location"` + ServerName string `json:"server_name" mapstructure:"server_name"` + IpAddress string `json:"ip_address" mapstructure:"ip_address"` +} diff --git a/console/service/internal/xdocker/images.go b/console/service/internal/xdocker/images.go new file mode 100644 index 000000000..0ab09496c --- /dev/null +++ b/console/service/internal/xdocker/images.go @@ -0,0 +1,7 @@ +package xdocker + +const ( + playbookCreateCluster = "deploy_pgcluster.yml" + + entryPoint = "ansible-playbook" +) diff --git a/console/service/internal/xdocker/imanager.go b/console/service/internal/xdocker/imanager.go new file mode 100644 index 000000000..0c609e0ea --- /dev/null +++ b/console/service/internal/xdocker/imanager.go @@ -0,0 +1,23 @@ +package xdocker + +import "context" + +type InstanceID string +type ManageClusterConfig struct { + Envs []string + ExtraVars []string + Mounts []Mount +} + +type Mount struct { + DockerPath string + HostPath string +} + +type IManager interface { + ManageCluster(ctx context.Context, req *ManageClusterConfig) (InstanceID, error) + GetStatus(ctx context.Context, id InstanceID) (string, error) + StoreContainerLogs(ctx context.Context, id InstanceID, store func(logMessage string)) + PreloadImage(ctx context.Context) + RemoveContainer(ctx context.Context, id InstanceID) error +} diff --git a/console/service/internal/xdocker/manager.go b/console/service/internal/xdocker/manager.go new file mode 100644 index 000000000..712e1c419 --- /dev/null +++ b/console/service/internal/xdocker/manager.go @@ -0,0 +1,158 @@ +package xdocker + +import ( + "bufio" + "context" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/client" + "github.com/goombaio/namegenerator" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "net/http" + "postgesql-cluster-console/pkg/tracer" + "time" +) + +type dockerManager struct { + cli *client.Client + log zerolog.Logger + image string +} + +func NewDockerManager(host string, image string) (IManager, error) { + var rt http.RoundTripper + rt, err := NewRoundTripperLog(host, log.Logger.With().Str("module", "docker_client").Logger()) + if err != nil { + return nil, err + } + cli, err := client.NewClientWithOpts( + client.WithHost(host), + client.WithHTTPClient(&http.Client{ + Transport: rt, + }), + client.WithAPIVersionNegotiation()) + if err != nil { + return nil, err + } + + return &dockerManager{ + cli: cli, + log: log.Logger.With().Str("module", "docker_manager").Logger(), + image: image, + }, nil +} + +func (m *dockerManager) ManageCluster(ctx context.Context, config *ManageClusterConfig) (InstanceID, error) { + localLog := m.log.With().Str("cid", ctx.Value(tracer.CtxCidKey{}).(string)).Logger() + err := m.pullImage(ctx, m.image) + if err != nil { + return "", err + } + + resp, err := m.cli.ContainerCreate(ctx, + &container.Config{ + Image: m.image, + Tty: true, + Env: config.Envs, + Cmd: func() []string { + cmd := []string{entryPoint, playbookCreateCluster} + for _, vars := range config.ExtraVars { + cmd = append(cmd, "--extra-vars", vars) + } + + return cmd + }(), + Entrypoint: nil, + }, &container.HostConfig{ + NetworkMode: "host", + Mounts: func() []mount.Mount { + var mounts []mount.Mount + for _, mountPath := range config.Mounts { + mounts = append(mounts, mount.Mount{ + Type: "bind", + Source: mountPath.HostPath, + Target: mountPath.DockerPath, + }) + } + + return mounts + }(), + }, nil, nil, namegenerator.NewNameGenerator(time.Now().UTC().UnixNano()).Generate()) + + if err != nil { + return "", err + } + + localLog.Trace().Str("id", resp.ID).Msg("container was created") + if len(resp.Warnings) != 0 { + localLog.Warn().Strs("warnings", resp.Warnings).Msg("warnings during container creation") + } + + err = m.cli.ContainerStart(ctx, resp.ID, container.StartOptions{}) + if err != nil { + errRem := m.cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{}) + if errRem != nil { + localLog.Error().Err(err).Msg("failed to remove container after error on start") + } + + return "", err + } + + return InstanceID(resp.ID), nil +} + +func (m *dockerManager) PreloadImage(ctx context.Context) { + _ = m.pullImage(ctx, m.image) +} + +func (m *dockerManager) GetStatus(ctx context.Context, id InstanceID) (string, error) { + inspectRes, err := m.cli.ContainerInspect(ctx, string(id)) + if err != nil { + return "", err + } + + return inspectRes.State.Status, nil +} + +func (m *dockerManager) StoreContainerLogs(ctx context.Context, ID InstanceID, store func(logMessage string)) { + localLog := m.log.With().Str("cid", ctx.Value(tracer.CtxCidKey{}).(string)).Logger() + localLog.Trace().Msg("StoreContainerLogs called") + hijackedCon, err := m.cli.ContainerAttach(ctx, string(ID), container.AttachOptions{ + Stream: true, + Stdin: false, + Stdout: true, + Stderr: true, + DetachKeys: "", + Logs: true, + }) + if err != nil { + localLog.Error().Err(err).Msg("failed to get container logs") + + return + } + localLog.Trace().Msg("got container logs") + defer func() { + hijackedCon.Close() + }() + + scanner := bufio.NewScanner(hijackedCon.Reader) + localLog.Trace().Msg("starting to scan logs") + for { + if ctx.Err() != nil { + localLog.Error().Err(ctx.Err()).Msg("ctx error") + break + } + if !scanner.Scan() { + localLog.Trace().Err(scanner.Err()).Msg("scanner scan returned false") + break + } + s := scanner.Text() + + store(s) + } +} + +func (m *dockerManager) RemoveContainer(ctx context.Context, id InstanceID) error { + return m.cli.ContainerRemove(ctx, string(id), container.RemoveOptions{}) +} diff --git a/console/service/internal/xdocker/manager_utils.go b/console/service/internal/xdocker/manager_utils.go new file mode 100644 index 000000000..a57908b4b --- /dev/null +++ b/console/service/internal/xdocker/manager_utils.go @@ -0,0 +1,43 @@ +package xdocker + +import ( + "context" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/errdefs" + "io" + "postgesql-cluster-console/pkg/tracer" + "strings" +) + +func (m *dockerManager) pullImage(ctx context.Context, dockerImage string) error { + localLog := m.log.With().Str("cid", ctx.Value(tracer.CtxCidKey{}).(string)).Logger() + inspectRes, _, err := m.cli.ImageInspectWithRaw(ctx, dockerImage) + if err != nil { + if _, ok := err.(errdefs.ErrNotFound); !ok { + localLog.Error().Err(err).Msg("failed to inspect docker image") + + return err + } + } + if err == nil && inspectRes.ID != "" { + return nil // already has locally + } + out, err := m.cli.ImagePull(ctx, dockerImage, image.PullOptions{}) + if err != nil { + localLog.Error().Err(err).Str("docker_image", dockerImage).Msg("failed to pull docker image") + + return err + } + defer func() { + err = out.Close() + if err != nil { + localLog.Warn().Err(err).Msg("failed to close image_pull output") + } + }() + + buf := strings.Builder{} + _, _ = io.Copy(&buf, out) + localLog.Trace().Str("log", buf.String()).Msg("pull image") + + return nil +} diff --git a/console/service/internal/xdocker/round_tripper_log.go b/console/service/internal/xdocker/round_tripper_log.go new file mode 100644 index 000000000..6456fad67 --- /dev/null +++ b/console/service/internal/xdocker/round_tripper_log.go @@ -0,0 +1,100 @@ +package xdocker + +import ( + "bytes" + "github.com/docker/docker/client" + "github.com/docker/go-connections/sockets" + "github.com/rs/zerolog" + "io" + "net/http" + "postgesql-cluster-console/pkg/tracer" +) + +type roundTripperLog struct { + http.Transport + log zerolog.Logger +} + +func NewRoundTripperLog(host string, log zerolog.Logger) (http.RoundTripper, error) { + rt := &roundTripperLog{ + log: log, + } + + hostURL, err := client.ParseHostURL(host) + if err != nil { + return nil, err + } + + err = sockets.ConfigureTransport(&rt.Transport, hostURL.Scheme, hostURL.Host) + if err != nil { + return nil, err + } + + return rt, nil +} + +func (rt *roundTripperLog) RoundTrip(request *http.Request) (*http.Response, error) { + var ( + copyBody io.ReadCloser + err error + ) + localLog := rt.log.With().Str("cid", request.Context().Value(tracer.CtxCidKey{}).(string)).Logger() + if request.Body != nil { + copyBody, err = request.GetBody() + if err != nil { + localLog.Error().Err(err).Msgf("failed to GetBody") + } else { + defer func() { + err = copyBody.Close() + if err != nil { + localLog.Error().Err(err).Msg("failed to close copy of body") + } + }() + body, err := io.ReadAll(copyBody) + if err != nil { + localLog.Error().Err(err).Msg("failed to ReadAll request body") + } else { + localLog.Trace().Str("url", request.URL.Path).Str("host", request.URL.Host).Str("method", request.Method).Str("body", string(body)).Msg("request body") + } + } + } else { + localLog.Trace().Str("url", request.URL.Path).Str("host", request.URL.Host).Str("method", request.Method).Msg("request") + } + + res, err := rt.Transport.RoundTrip(request) + if err != nil { + localLog.Error().Err(err).Msg("failed to RoundTrip") + } else { + var respBody io.ReadCloser + respBody, res.Body, err = drainBody(res.Body) + if err != nil { + localLog.Error().Err(err).Msg("failed to drain body") + } else { + defer func() { + err = respBody.Close() + if err != nil { + localLog.Error().Err(err).Msg("failed to close response body") + } + }() + body, err := io.ReadAll(respBody) + if err != nil { + localLog.Error().Err(err).Msg("failed to ReadAll response body") + } else { + localLog.Trace().Str("url", request.URL.Path).Str("host", request.URL.Host).Str("body", string(body)).Msg("response body") + } + } + } + + return res, err +} + +func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { + var buf bytes.Buffer + if _, err = buf.ReadFrom(b); err != nil { + return nil, b, err + } + if err = b.Close(); err != nil { + return nil, b, err + } + return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil +} diff --git a/console/service/main.go b/console/service/main.go new file mode 100644 index 000000000..b970a16f8 --- /dev/null +++ b/console/service/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "context" + _ "embed" + "fmt" + "os" + "postgesql-cluster-console/internal/configuration" + "postgesql-cluster-console/internal/db" + "postgesql-cluster-console/internal/service" + "postgesql-cluster-console/internal/storage" + "postgesql-cluster-console/internal/watcher" + "postgesql-cluster-console/internal/xdocker" + "postgesql-cluster-console/migrations" + "postgesql-cluster-console/pkg/patroni" + "postgesql-cluster-console/pkg/tracer" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +//go:embed VERSION +var Version string + +const appName = "pg_console" + +func init() { + log.Logger = zerolog.New(os.Stdout).With(). + Timestamp(). + Str("app", appName). + Str("version", Version). + Logger() +} + +func main() { + if len(os.Args) > 1 { + configuration.PrintUsage() + + return + } + + cfg, err := configuration.ReadConfig() + if err != nil { + fmt.Print(err.Error()) + + return + } + log.Info().Interface("config", cfg).Msg("config was parsed") + + l, err := zerolog.ParseLevel(cfg.Logger.Level) + if err != nil { + log.Error().Str("log_level", cfg.Logger.Level).Msg("unknown log level") + } else { + zerolog.SetGlobalLevel(l) + log.Info().Str("log_level", cfg.Logger.Level).Msg("log level was set") + } + + dbPool, err := db.NewDbPool(cfg) + if err != nil { + log.Error().Err(err).Msg("failed to create db pool") + + return + } + + err = migrations.Migrate(dbPool, cfg.Db.MigrationDir) + if err != nil { + log.Error().Err(err).Msg("failed to make db migration") + + return + } + + str := storage.NewDbStorage(dbPool) + dockerManager, err := xdocker.NewDockerManager(cfg.Docker.Host, cfg.Docker.Image) + if err != nil { + log.Error().Err(err).Msg("failed to create docker manager") + + return + } + + ctx, cancel := context.WithCancel(context.WithValue(context.Background(), tracer.CtxCidKey{}, "")) + go func() { + log.Info().Msgf("preload docker image: %s", cfg.Docker.Image) + dockerManager.PreloadImage(ctx) + }() + defer cancel() + + logWatcher := watcher.NewLogWatcher(str, dockerManager, cfg) + logWatcher.Run() + defer logWatcher.Stop() + + logAggregator := watcher.NewLogCollector(str, dockerManager) + defer logAggregator.Stop() + + clusterWatcher := watcher.NewServerWatcher(str, patroni.NewClient(log.Logger), cfg) + clusterWatcher.Run() + defer clusterWatcher.Stop() + + s, err := service.NewService(cfg, Version, str, dockerManager, logAggregator, clusterWatcher) + if err != nil { + log.Error().Err(err).Msg("failed to create service") + + return + } + + err = s.Serve() + if err != nil { + log.Error().Err(err).Msg("service was finished with error") + } else { + log.Info().Msg("service was successfully stopped") + } +} diff --git a/console/service/middleware/authorization.go b/console/service/middleware/authorization.go new file mode 100644 index 000000000..b6b9a55ab --- /dev/null +++ b/console/service/middleware/authorization.go @@ -0,0 +1,34 @@ +package middleware + +import ( + "encoding/json" + "fmt" + "net/http" + "postgesql-cluster-console/models" + "strings" +) + +func Authorization(token string, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + const ( + headerName = "Authorization" + schemeName = "Bearer" + ) + tokenVal := r.Header.Get(headerName) + tokenValSplit := strings.Split(tokenVal, " ") + if len(tokenValSplit) != 2 || tokenValSplit[0] != schemeName || tokenValSplit[1] != token { + w.Header().Add("content-type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + resp, _ := json.Marshal(&models.ResponseError{ + Code: http.StatusUnauthorized, + Description: fmt.Sprintf("token [%s] invalid", tokenVal), + Title: "Invalid token", + }) + _, _ = w.Write(resp) + + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/console/service/middleware/cid.go b/console/service/middleware/cid.go new file mode 100644 index 000000000..2cc5509d6 --- /dev/null +++ b/console/service/middleware/cid.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "context" + "github.com/google/uuid" + "net/http" + "postgesql-cluster-console/pkg/tracer" +) + +func SetCorrelationId(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cid := getCid(r) + if r.Header.Get(XCorrID) == "" { + r.Header.Set(XCorrID, cid) + } + + ctx := context.WithValue(r.Context(), tracer.CtxCidKey{}, cid) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func getCid(r *http.Request) string { + cid := r.Header.Get(XCorrID) + if cid != "" { + return cid + } + + return uuid.New().String() +} diff --git a/console/service/middleware/cors.go b/console/service/middleware/cors.go new file mode 100644 index 000000000..82d415ade --- /dev/null +++ b/console/service/middleware/cors.go @@ -0,0 +1,19 @@ +package middleware + +import "net/http" + +func CORS(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + w.Header().Set("Access-Control-Allow-Credentials", "true") + w.Header().Set("Access-Control-Allow-Methods", " GET, POST, OPTIONS, PATCH, DELETE, PUT") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Expose-Headers", "X-Log-Completed, X-Cluster-Id") + w.Header().Set("Access-Control-Allow-Headers", "Authorization, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Origin,Accept, "+ + "X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, X-Log-Completed, X-Cluster-Id") + + if r.Method != http.MethodOptions { + next.ServeHTTP(w, r) + } + }) +} diff --git a/console/service/middleware/request_log.go b/console/service/middleware/request_log.go new file mode 100644 index 000000000..2d0d52eef --- /dev/null +++ b/console/service/middleware/request_log.go @@ -0,0 +1,166 @@ +package middleware + +import ( + "bytes" + "encoding/json" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "io" + "net/http" + "time" +) + +const XCorrID = "X-Correlation-Id" + +const responseWriterBodyLimit = 10000 + +type responseWriter struct { + http.ResponseWriter + body []byte + statusCode int + bodyOverflow bool +} + +func GetResponseWriterCode(w http.ResponseWriter) int { + if wNew, ok := w.(*responseWriter); ok { + return wNew.statusCode + } + return 0 +} + +func (r *responseWriter) Write(b []byte) (int, error) { + if len(r.body) < responseWriterBodyLimit { + maxWriteLen := responseWriterBodyLimit - len(r.body) + if len(b) > maxWriteLen { + r.body = append(r.body, b[:maxWriteLen]...) + r.bodyOverflow = true + } else { + r.body = append(r.body, b...) + } + } + + return r.ResponseWriter.Write(b) +} + +func (r *responseWriter) WriteHeader(statusCode int) { + r.statusCode = statusCode + r.ResponseWriter.WriteHeader(statusCode) +} + +func (r *responseWriter) zerologResponse(log zerolog.Logger) { + body := r.prepareBodyForLog() + + log.Debug(). + Any("body", body). + Any("headers", r.Header()). + Int("status", r.getStatusCode()).Msg("[zerologResponse] Response was sent") +} + +func (r *responseWriter) getStatusCode() int { + if r.statusCode == 0 { + return 200 + } + + return r.statusCode +} + +func (r *responseWriter) prepareBodyForLog() any { + var body map[string]interface{} + if r.bodyOverflow { + return r.body + } + _ = json.Unmarshal(r.body, &body) + replaceFields(body, secretFields) + + return body +} + +var secretFields = map[string]string{ + "AWS_SECRET_ACCESS_KEY": "***", + "GCP_SERVICE_ACCOUNT_CONTENTS": "***", + "HCLOUD_API_TOKEN": "***", + "SSH_PRIVATE_KEY": "***", + "DO_API_TOKEN": "***", + "PASSWORD": "***", + "password": "***", + "AZURE_SECRET": "***", +} + +func RequestZeroLog(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cid := r.Header.Get(XCorrID) + clog := log.With(). + Str("cid", cid). + Str("method", r.Method). + Str("path", r.URL.String()). + Str("protocol", r.Proto). + Int64("request_length", r.ContentLength). + Logger() + + var ( + body []byte + bodyInt map[string]interface{} + bodyCopy io.ReadCloser + err error + ) + + if r.Body != nil && r.Body != http.NoBody { + bodyCopy, r.Body, err = drainBody(r.Body) + if err == nil { + body, err = io.ReadAll(bodyCopy) + if err != nil { + clog.Error().Err(err).Msg("[RequestZeroLog] read body error") + } + } else { + clog.Error().Err(err).Msg("[RequestZeroLog] drainBody failed") + } + } + + err = json.Unmarshal(body, &bodyInt) + if err != nil { + clog.Debug(). + Any("headers", r.Header). + Any("query", r.URL.Query()). + Bytes("body", body). + Msg("[RequestLog] request accepted") + } else { + replaceFields(bodyInt, secretFields) + clog.Debug(). + Any("headers", r.Header). + Any("query", r.URL.Query()). + Any("body", bodyInt). + Msg("[RequestLog] request accepted") + } + + w.Header().Set(XCorrID, cid) + + start := time.Now() + wExt := &responseWriter{ResponseWriter: w} + next.ServeHTTP(wExt, r) + duration := time.Since(start) + + wExt.zerologResponse(clog) + + clog.Debug(). + Int("status", wExt.getStatusCode()). + Dur("handle_time", duration). // request_time + Int("response_length", len(wExt.body)). + Msg("[RequestLog] request completed") + }) +} + +// drainBody reads all of b to memory and then returns two equivalent +// ReadClosers yielding the same bytes. +// +// It returns an error if the initial slurp of all bytes fails. It does not attempt +// to make the returned ReadClosers have identical error-matching behavior. +func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { + var buf bytes.Buffer + if _, err = buf.ReadFrom(b); err != nil { + return nil, b, err + } + if err = b.Close(); err != nil { + return nil, b, err + } + return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil +} diff --git a/console/service/middleware/utils.go b/console/service/middleware/utils.go new file mode 100644 index 000000000..02170315e --- /dev/null +++ b/console/service/middleware/utils.go @@ -0,0 +1,33 @@ +package middleware + +import "reflect" + +func replaceFields(data map[string]interface{}, replacements map[string]string) { + for key, val := range data { + if replacement, ok := replacements[key]; ok { + data[key] = replacement + } else { + valReflect := reflect.ValueOf(val) + switch valReflect.Kind() { + case reflect.Map: + if innerMap, ok := val.(map[string]interface{}); ok { + replaceFields(innerMap, replacements) + } + case reflect.Slice: + for j := 0; j < valReflect.Len(); j++ { + elemVal := valReflect.Index(j) + if innerElemMap, ok := elemVal.Interface().(map[string]interface{}); ok { + replaceFields(innerElemMap, replacements) + } + } + case reflect.Ptr: + if !valReflect.IsNil() { + elem := valReflect.Elem().Interface() + if innerMap, ok := elem.(map[string]interface{}); ok { + replaceFields(innerMap, replacements) + } + } + } + } + } +} diff --git a/console/service/migrations/goose_logger.go b/console/service/migrations/goose_logger.go new file mode 100644 index 000000000..fac91c5c6 --- /dev/null +++ b/console/service/migrations/goose_logger.go @@ -0,0 +1,22 @@ +package migrations + +import ( + "github.com/pressly/goose/v3" + "github.com/rs/zerolog" +) + +type zeroLogAdapter struct { + log zerolog.Logger +} + +func NewZeroLogAdapter(log zerolog.Logger) goose.Logger { + return zeroLogAdapter{log: log.With().Str("module", "goouse").Logger()} +} + +func (l zeroLogAdapter) Fatalf(format string, v ...interface{}) { + l.log.Error().Msgf(format, v...) +} + +func (l zeroLogAdapter) Printf(format string, v ...interface{}) { + l.log.Info().Msgf(format, v...) +} diff --git a/console/service/migrations/migrate.go b/console/service/migrations/migrate.go new file mode 100644 index 000000000..7dc85064c --- /dev/null +++ b/console/service/migrations/migrate.go @@ -0,0 +1,18 @@ +package migrations + +import ( + "context" + + "github.com/rs/zerolog/log" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" + "github.com/pressly/goose/v3" +) + +func Migrate(dbPool *pgxpool.Pool, migrationDir string) error { + db := stdlib.OpenDBFromPool(dbPool) + goose.SetLogger(NewZeroLogAdapter(log.Logger)) + + return goose.RunContext(context.Background(), "up", db, migrationDir) +} diff --git a/console/service/models/cluster_info.go b/console/service/models/cluster_info.go new file mode 100644 index 000000000..233223846 --- /dev/null +++ b/console/service/models/cluster_info.go @@ -0,0 +1,170 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ClusterInfo Cluster info +// +// swagger:model ClusterInfo +type ClusterInfo struct { + + // Code of location + // Example: eu-north-1 + ClusterLocation string `json:"cluster_location,omitempty"` + + // connection info + ConnectionInfo interface{} `json:"connection_info,omitempty"` + + // creation time + // Example: 16.10.2023T11:20:00Z + // Format: date-time + CreationTime strfmt.DateTime `json:"creation_time,omitempty"` + + // description + Description string `json:"description,omitempty"` + + // environment + // Example: production + Environment string `json:"environment,omitempty"` + + // id + ID int64 `json:"id,omitempty"` + + // name + // Example: drm-prod-pgcluster + Name string `json:"name,omitempty"` + + // postgres version + // Example: 15 + PostgresVersion int32 `json:"postgres_version,omitempty"` + + // Project for cluster + ProjectName string `json:"project_name,omitempty"` + + // servers + Servers []*ClusterInfoInstance `json:"servers"` + + // status + // Example: healthy + Status string `json:"status,omitempty"` +} + +// Validate validates this cluster info +func (m *ClusterInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreationTime(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServers(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ClusterInfo) validateCreationTime(formats strfmt.Registry) error { + if swag.IsZero(m.CreationTime) { // not required + return nil + } + + if err := validate.FormatOf("creation_time", "body", "date-time", m.CreationTime.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *ClusterInfo) validateServers(formats strfmt.Registry) error { + if swag.IsZero(m.Servers) { // not required + return nil + } + + for i := 0; i < len(m.Servers); i++ { + if swag.IsZero(m.Servers[i]) { // not required + continue + } + + if m.Servers[i] != nil { + if err := m.Servers[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("servers" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("servers" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this cluster info based on the context it is used +func (m *ClusterInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateServers(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ClusterInfo) contextValidateServers(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Servers); i++ { + + if m.Servers[i] != nil { + if err := m.Servers[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("servers" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("servers" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ClusterInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ClusterInfo) UnmarshalBinary(b []byte) error { + var res ClusterInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/cluster_info_additional_settings.go b/console/service/models/cluster_info_additional_settings.go new file mode 100644 index 000000000..4b02d0dd3 --- /dev/null +++ b/console/service/models/cluster_info_additional_settings.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ClusterInfoAdditionalSettings Additional settings for cluster +// +// swagger:model ClusterInfo.AdditionalSettings +type ClusterInfoAdditionalSettings struct { + + // connection info + ConnectionInfo interface{} `json:"connection_info,omitempty"` +} + +// Validate validates this cluster info additional settings +func (m *ClusterInfoAdditionalSettings) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this cluster info additional settings based on context it is used +func (m *ClusterInfoAdditionalSettings) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ClusterInfoAdditionalSettings) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ClusterInfoAdditionalSettings) UnmarshalBinary(b []byte) error { + var res ClusterInfoAdditionalSettings + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/cluster_info_instance.go b/console/service/models/cluster_info_instance.go new file mode 100644 index 000000000..1a97c3868 --- /dev/null +++ b/console/service/models/cluster_info_instance.go @@ -0,0 +1,80 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ClusterInfoInstance Instance info for current cluster +// +// swagger:model ClusterInfo.Instance +type ClusterInfoInstance struct { + + // id + ID int64 `json:"id,omitempty"` + + // ip + // Example: 10.128.64.141 + IP string `json:"ip,omitempty"` + + // lag + // Example: 0 + Lag *int64 `json:"lag,omitempty"` + + // name + // Example: pgnode1 + Name string `json:"name,omitempty"` + + // pending restart + // Example: false + PendingRestart *bool `json:"pending_restart,omitempty"` + + // role + // Example: leader + Role string `json:"role,omitempty"` + + // status + Status string `json:"status,omitempty"` + + // tags + Tags interface{} `json:"tags,omitempty"` + + // timeline + // Example: 1 + Timeline *int64 `json:"timeline,omitempty"` +} + +// Validate validates this cluster info instance +func (m *ClusterInfoInstance) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this cluster info instance based on context it is used +func (m *ClusterInfoInstance) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ClusterInfoInstance) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ClusterInfoInstance) UnmarshalBinary(b []byte) error { + var res ClusterInfoInstance + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/deployment_cloud_image.go b/console/service/models/deployment_cloud_image.go new file mode 100644 index 000000000..0bd4a7877 --- /dev/null +++ b/console/service/models/deployment_cloud_image.go @@ -0,0 +1,90 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// DeploymentCloudImage deployment cloud image +// +// swagger:model Deployment.CloudImage +type DeploymentCloudImage struct { + + // arch + // Example: amd64 + Arch string `json:"arch,omitempty"` + + // image + // Example: {\"server_image\": \"ami-078b3985bbc361448\"} + Image interface{} `json:"image,omitempty"` + + // os name + // Example: Ubuntu + OsName string `json:"os_name,omitempty"` + + // os version + // Example: 22.04 LTS + OsVersion string `json:"os_version,omitempty"` + + // updated at + // Format: datetime + UpdatedAt strfmt.DateTime `json:"updated_at,omitempty"` +} + +// Validate validates this deployment cloud image +func (m *DeploymentCloudImage) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DeploymentCloudImage) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updated_at", "body", "datetime", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this deployment cloud image based on context it is used +func (m *DeploymentCloudImage) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *DeploymentCloudImage) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DeploymentCloudImage) UnmarshalBinary(b []byte) error { + var res DeploymentCloudImage + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/deployment_info_cloud_region.go b/console/service/models/deployment_info_cloud_region.go new file mode 100644 index 000000000..cca8921f3 --- /dev/null +++ b/console/service/models/deployment_info_cloud_region.go @@ -0,0 +1,222 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// DeploymentInfoCloudRegion deployment info cloud region +// +// swagger:model DeploymentInfo.CloudRegion +type DeploymentInfoCloudRegion struct { + + // unique parameter for DB + // Example: north_america + Code string `json:"code,omitempty"` + + // List of datacenters for this region + Datacenters []*DeploymentInfoCloudRegionDatacentersItems0 `json:"datacenters"` + + // Field for web + // Example: North America + Name string `json:"name,omitempty"` +} + +// Validate validates this deployment info cloud region +func (m *DeploymentInfoCloudRegion) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateDatacenters(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DeploymentInfoCloudRegion) validateDatacenters(formats strfmt.Registry) error { + if swag.IsZero(m.Datacenters) { // not required + return nil + } + + for i := 0; i < len(m.Datacenters); i++ { + if swag.IsZero(m.Datacenters[i]) { // not required + continue + } + + if m.Datacenters[i] != nil { + if err := m.Datacenters[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("datacenters" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("datacenters" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this deployment info cloud region based on the context it is used +func (m *DeploymentInfoCloudRegion) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateDatacenters(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DeploymentInfoCloudRegion) contextValidateDatacenters(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Datacenters); i++ { + + if m.Datacenters[i] != nil { + if err := m.Datacenters[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("datacenters" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("datacenters" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *DeploymentInfoCloudRegion) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DeploymentInfoCloudRegion) UnmarshalBinary(b []byte) error { + var res DeploymentInfoCloudRegion + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// DeploymentInfoCloudRegionDatacentersItems0 deployment info cloud region datacenters items0 +// +// swagger:model DeploymentInfoCloudRegionDatacentersItems0 +type DeploymentInfoCloudRegionDatacentersItems0 struct { + + // cloud image + CloudImage *DeploymentCloudImage `json:"cloud_image,omitempty"` + + // code + // Example: ca-central-1 + Code string `json:"code,omitempty"` + + // location + // Example: Canada (central) + Location string `json:"location,omitempty"` +} + +// Validate validates this deployment info cloud region datacenters items0 +func (m *DeploymentInfoCloudRegionDatacentersItems0) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCloudImage(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DeploymentInfoCloudRegionDatacentersItems0) validateCloudImage(formats strfmt.Registry) error { + if swag.IsZero(m.CloudImage) { // not required + return nil + } + + if m.CloudImage != nil { + if err := m.CloudImage.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("cloud_image") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("cloud_image") + } + return err + } + } + + return nil +} + +// ContextValidate validate this deployment info cloud region datacenters items0 based on the context it is used +func (m *DeploymentInfoCloudRegionDatacentersItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCloudImage(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *DeploymentInfoCloudRegionDatacentersItems0) contextValidateCloudImage(ctx context.Context, formats strfmt.Registry) error { + + if m.CloudImage != nil { + if err := m.CloudImage.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("cloud_image") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("cloud_image") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *DeploymentInfoCloudRegionDatacentersItems0) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DeploymentInfoCloudRegionDatacentersItems0) UnmarshalBinary(b []byte) error { + var res DeploymentInfoCloudRegionDatacentersItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/deployment_instance_type.go b/console/service/models/deployment_instance_type.go new file mode 100644 index 000000000..4be9bf8e6 --- /dev/null +++ b/console/service/models/deployment_instance_type.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// DeploymentInstanceType deployment instance type +// +// swagger:model Deployment.InstanceType +type DeploymentInstanceType struct { + + // code + // Example: m5.2xlarge + Code string `json:"code,omitempty"` + + // cpu + // Example: 8 + CPU int64 `json:"cpu,omitempty"` + + // Price currency + // Example: $ + Currency string `json:"currency,omitempty"` + + // Price for 1 instance by hour + // Example: 0.01 + PriceHourly float64 `json:"price_hourly,omitempty"` + + // Price for 1 instance by month + // Example: 1.2 + PriceMonthly float64 `json:"price_monthly,omitempty"` + + // ram + // Example: 256 + RAM int64 `json:"ram,omitempty"` +} + +// Validate validates this deployment instance type +func (m *DeploymentInstanceType) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this deployment instance type based on context it is used +func (m *DeploymentInstanceType) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *DeploymentInstanceType) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DeploymentInstanceType) UnmarshalBinary(b []byte) error { + var res DeploymentInstanceType + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/meta_pagination.go b/console/service/models/meta_pagination.go new file mode 100644 index 000000000..e52271aed --- /dev/null +++ b/console/service/models/meta_pagination.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// MetaPagination Pagination info for list requests +// +// swagger:model Meta.Pagination +type MetaPagination struct { + + // count + Count *int64 `json:"count,omitempty"` + + // limit + Limit *int64 `json:"limit,omitempty"` + + // offset + Offset *int64 `json:"offset,omitempty"` +} + +// Validate validates this meta pagination +func (m *MetaPagination) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this meta pagination based on context it is used +func (m *MetaPagination) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MetaPagination) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MetaPagination) UnmarshalBinary(b []byte) error { + var res MetaPagination + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_change_setting.go b/console/service/models/request_change_setting.go new file mode 100644 index 000000000..5a0a053b9 --- /dev/null +++ b/console/service/models/request_change_setting.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestChangeSetting Change setting +// +// swagger:model Request.ChangeSetting +type RequestChangeSetting struct { + + // value + Value interface{} `json:"value,omitempty"` +} + +// Validate validates this request change setting +func (m *RequestChangeSetting) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request change setting based on context it is used +func (m *RequestChangeSetting) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestChangeSetting) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestChangeSetting) UnmarshalBinary(b []byte) error { + var res RequestChangeSetting + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_cluster_create.go b/console/service/models/request_cluster_create.go new file mode 100644 index 000000000..195764990 --- /dev/null +++ b/console/service/models/request_cluster_create.go @@ -0,0 +1,161 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestClusterCreate Request struct for cluster creation +// +// swagger:model Request.ClusterCreate +type RequestClusterCreate struct { + + // auth info + AuthInfo *RequestClusterCreateAuthInfo `json:"auth_info,omitempty"` + + // Info about cluster + Description string `json:"description,omitempty"` + + // Project environment + EnvironmentID int64 `json:"environment_id,omitempty"` + + // envs + Envs []string `json:"envs"` + + // extra vars + ExtraVars []string `json:"extra_vars"` + + // name + // Example: drm-prod-pgcluster + Name string `json:"name,omitempty"` + + // Project for new cluster + ProjectID int64 `json:"project_id,omitempty"` +} + +// Validate validates this request cluster create +func (m *RequestClusterCreate) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAuthInfo(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RequestClusterCreate) validateAuthInfo(formats strfmt.Registry) error { + if swag.IsZero(m.AuthInfo) { // not required + return nil + } + + if m.AuthInfo != nil { + if err := m.AuthInfo.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("auth_info") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("auth_info") + } + return err + } + } + + return nil +} + +// ContextValidate validate this request cluster create based on the context it is used +func (m *RequestClusterCreate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateAuthInfo(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RequestClusterCreate) contextValidateAuthInfo(ctx context.Context, formats strfmt.Registry) error { + + if m.AuthInfo != nil { + if err := m.AuthInfo.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("auth_info") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("auth_info") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *RequestClusterCreate) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestClusterCreate) UnmarshalBinary(b []byte) error { + var res RequestClusterCreate + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// RequestClusterCreateAuthInfo Info for deployment system authorization +// +// swagger:model RequestClusterCreateAuthInfo +type RequestClusterCreateAuthInfo struct { + + // secret id + // Example: 1 + SecretID int64 `json:"secret_id,omitempty"` +} + +// Validate validates this request cluster create auth info +func (m *RequestClusterCreateAuthInfo) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request cluster create auth info based on context it is used +func (m *RequestClusterCreateAuthInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestClusterCreateAuthInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestClusterCreateAuthInfo) UnmarshalBinary(b []byte) error { + var res RequestClusterCreateAuthInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_cluster_reinit.go b/console/service/models/request_cluster_reinit.go new file mode 100644 index 000000000..0491689df --- /dev/null +++ b/console/service/models/request_cluster_reinit.go @@ -0,0 +1,11 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// RequestClusterReinit Reinit cluster +// +// swagger:model Request.ClusterReinit +type RequestClusterReinit interface{} diff --git a/console/service/models/request_cluster_reload.go b/console/service/models/request_cluster_reload.go new file mode 100644 index 000000000..57fc4354c --- /dev/null +++ b/console/service/models/request_cluster_reload.go @@ -0,0 +1,11 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// RequestClusterReload Reload cluster +// +// swagger:model Request.ClusterReload +type RequestClusterReload interface{} diff --git a/console/service/models/request_cluster_remove.go b/console/service/models/request_cluster_remove.go new file mode 100644 index 000000000..7eb574a52 --- /dev/null +++ b/console/service/models/request_cluster_remove.go @@ -0,0 +1,11 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// RequestClusterRemove Remove cluster +// +// swagger:model Request.ClusterRemove +type RequestClusterRemove interface{} diff --git a/console/service/models/request_cluster_restart.go b/console/service/models/request_cluster_restart.go new file mode 100644 index 000000000..fbcba61bd --- /dev/null +++ b/console/service/models/request_cluster_restart.go @@ -0,0 +1,11 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// RequestClusterRestart Restart cluster +// +// swagger:model Request.ClusterRestart +type RequestClusterRestart interface{} diff --git a/console/service/models/request_cluster_start.go b/console/service/models/request_cluster_start.go new file mode 100644 index 000000000..666099543 --- /dev/null +++ b/console/service/models/request_cluster_start.go @@ -0,0 +1,11 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// RequestClusterStart Start cluster +// +// swagger:model Request.ClusterStart +type RequestClusterStart interface{} diff --git a/console/service/models/request_cluster_stop.go b/console/service/models/request_cluster_stop.go new file mode 100644 index 000000000..f9eceb26c --- /dev/null +++ b/console/service/models/request_cluster_stop.go @@ -0,0 +1,11 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// RequestClusterStop Stop cluster +// +// swagger:model Request.ClusterStop +type RequestClusterStop interface{} diff --git a/console/service/models/request_create_setting.go b/console/service/models/request_create_setting.go new file mode 100644 index 000000000..62c9d4288 --- /dev/null +++ b/console/service/models/request_create_setting.go @@ -0,0 +1,53 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestCreateSetting Create new setting +// +// swagger:model Request.CreateSetting +type RequestCreateSetting struct { + + // name + Name string `json:"name,omitempty"` + + // value + Value interface{} `json:"value,omitempty"` +} + +// Validate validates this request create setting +func (m *RequestCreateSetting) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request create setting based on context it is used +func (m *RequestCreateSetting) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestCreateSetting) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestCreateSetting) UnmarshalBinary(b []byte) error { + var res RequestCreateSetting + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_environment.go b/console/service/models/request_environment.go new file mode 100644 index 000000000..8f37d7ec4 --- /dev/null +++ b/console/service/models/request_environment.go @@ -0,0 +1,55 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestEnvironment request environment +// +// swagger:model Request.Environment +type RequestEnvironment struct { + + // description + // Example: environment for production + Description string `json:"description,omitempty"` + + // name + // Example: production + Name string `json:"name,omitempty"` +} + +// Validate validates this request environment +func (m *RequestEnvironment) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request environment based on context it is used +func (m *RequestEnvironment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestEnvironment) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestEnvironment) UnmarshalBinary(b []byte) error { + var res RequestEnvironment + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_project_create.go b/console/service/models/request_project_create.go new file mode 100644 index 000000000..7d21b9ce8 --- /dev/null +++ b/console/service/models/request_project_create.go @@ -0,0 +1,55 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestProjectCreate request project create +// +// swagger:model Request.ProjectCreate +type RequestProjectCreate struct { + + // description + // Example: Default project + Description string `json:"description,omitempty"` + + // name + // Example: default + Name string `json:"name,omitempty"` +} + +// Validate validates this request project create +func (m *RequestProjectCreate) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request project create based on context it is used +func (m *RequestProjectCreate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestProjectCreate) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestProjectCreate) UnmarshalBinary(b []byte) error { + var res RequestProjectCreate + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_project_patch.go b/console/service/models/request_project_patch.go new file mode 100644 index 000000000..170438db8 --- /dev/null +++ b/console/service/models/request_project_patch.go @@ -0,0 +1,53 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestProjectPatch request project patch +// +// swagger:model Request.ProjectPatch +type RequestProjectPatch struct { + + // description + Description *string `json:"description,omitempty"` + + // name + Name *string `json:"name,omitempty"` +} + +// Validate validates this request project patch +func (m *RequestProjectPatch) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request project patch based on context it is used +func (m *RequestProjectPatch) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestProjectPatch) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestProjectPatch) UnmarshalBinary(b []byte) error { + var res RequestProjectPatch + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_create.go b/console/service/models/request_secret_create.go new file mode 100644 index 000000000..06619ec58 --- /dev/null +++ b/console/service/models/request_secret_create.go @@ -0,0 +1,154 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretCreate request secret create +// +// swagger:model Request.SecretCreate +type RequestSecretCreate struct { + + // name + // Example: aws key + Name string `json:"name,omitempty"` + + // project id + // Example: 1 + ProjectID int64 `json:"project_id,omitempty"` + + // type + Type SecretType `json:"type,omitempty"` + + // value + Value *RequestSecretValue `json:"value,omitempty"` +} + +// Validate validates this request secret create +func (m *RequestSecretCreate) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateType(formats); err != nil { + res = append(res, err) + } + + if err := m.validateValue(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RequestSecretCreate) validateType(formats strfmt.Registry) error { + if swag.IsZero(m.Type) { // not required + return nil + } + + if err := m.Type.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("type") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("type") + } + return err + } + + return nil +} + +func (m *RequestSecretCreate) validateValue(formats strfmt.Registry) error { + if swag.IsZero(m.Value) { // not required + return nil + } + + if m.Value != nil { + if err := m.Value.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("value") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("value") + } + return err + } + } + + return nil +} + +// ContextValidate validate this request secret create based on the context it is used +func (m *RequestSecretCreate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateType(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateValue(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RequestSecretCreate) contextValidateType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.Type.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("type") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("type") + } + return err + } + + return nil +} + +func (m *RequestSecretCreate) contextValidateValue(ctx context.Context, formats strfmt.Registry) error { + + if m.Value != nil { + if err := m.Value.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("value") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("value") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretCreate) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretCreate) UnmarshalBinary(b []byte) error { + var res RequestSecretCreate + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_patch.go b/console/service/models/request_secret_patch.go new file mode 100644 index 000000000..e981ec55b --- /dev/null +++ b/console/service/models/request_secret_patch.go @@ -0,0 +1,59 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretPatch request secret patch +// +// swagger:model Request.SecretPatch +type RequestSecretPatch struct { + + // name + // Example: aws key + Name *string `json:"name,omitempty"` + + // type + // Example: aws + Type *string `json:"type,omitempty"` + + // Secret value in base64 + // Example: c2VjcmV0 + Value *string `json:"value,omitempty"` +} + +// Validate validates this request secret patch +func (m *RequestSecretPatch) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret patch based on context it is used +func (m *RequestSecretPatch) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretPatch) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretPatch) UnmarshalBinary(b []byte) error { + var res RequestSecretPatch + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value.go b/console/service/models/request_secret_value.go new file mode 100644 index 000000000..7fc4e43f6 --- /dev/null +++ b/console/service/models/request_secret_value.go @@ -0,0 +1,380 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValue request secret value +// +// swagger:model Request.SecretValue +type RequestSecretValue struct { + + // aws + Aws *RequestSecretValueAws `json:"aws,omitempty"` + + // azure + Azure *RequestSecretValueAzure `json:"azure,omitempty"` + + // digitalocean + Digitalocean *RequestSecretValueDigitalOcean `json:"digitalocean,omitempty"` + + // gcp + Gcp *RequestSecretValueGcp `json:"gcp,omitempty"` + + // hetzner + Hetzner *RequestSecretValueHetzner `json:"hetzner,omitempty"` + + // password + Password *RequestSecretValuePassword `json:"password,omitempty"` + + // ssh key + SSHKey *RequestSecretValueSSHKey `json:"ssh_key,omitempty"` +} + +// Validate validates this request secret value +func (m *RequestSecretValue) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAws(formats); err != nil { + res = append(res, err) + } + + if err := m.validateAzure(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDigitalocean(formats); err != nil { + res = append(res, err) + } + + if err := m.validateGcp(formats); err != nil { + res = append(res, err) + } + + if err := m.validateHetzner(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePassword(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSSHKey(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RequestSecretValue) validateAws(formats strfmt.Registry) error { + if swag.IsZero(m.Aws) { // not required + return nil + } + + if m.Aws != nil { + if err := m.Aws.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("aws") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("aws") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) validateAzure(formats strfmt.Registry) error { + if swag.IsZero(m.Azure) { // not required + return nil + } + + if m.Azure != nil { + if err := m.Azure.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("azure") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("azure") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) validateDigitalocean(formats strfmt.Registry) error { + if swag.IsZero(m.Digitalocean) { // not required + return nil + } + + if m.Digitalocean != nil { + if err := m.Digitalocean.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("digitalocean") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("digitalocean") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) validateGcp(formats strfmt.Registry) error { + if swag.IsZero(m.Gcp) { // not required + return nil + } + + if m.Gcp != nil { + if err := m.Gcp.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("gcp") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("gcp") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) validateHetzner(formats strfmt.Registry) error { + if swag.IsZero(m.Hetzner) { // not required + return nil + } + + if m.Hetzner != nil { + if err := m.Hetzner.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("hetzner") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("hetzner") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) validatePassword(formats strfmt.Registry) error { + if swag.IsZero(m.Password) { // not required + return nil + } + + if m.Password != nil { + if err := m.Password.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("password") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("password") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) validateSSHKey(formats strfmt.Registry) error { + if swag.IsZero(m.SSHKey) { // not required + return nil + } + + if m.SSHKey != nil { + if err := m.SSHKey.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ssh_key") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ssh_key") + } + return err + } + } + + return nil +} + +// ContextValidate validate this request secret value based on the context it is used +func (m *RequestSecretValue) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateAws(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateAzure(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDigitalocean(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateGcp(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateHetzner(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidatePassword(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSSHKey(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RequestSecretValue) contextValidateAws(ctx context.Context, formats strfmt.Registry) error { + + if m.Aws != nil { + if err := m.Aws.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("aws") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("aws") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) contextValidateAzure(ctx context.Context, formats strfmt.Registry) error { + + if m.Azure != nil { + if err := m.Azure.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("azure") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("azure") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) contextValidateDigitalocean(ctx context.Context, formats strfmt.Registry) error { + + if m.Digitalocean != nil { + if err := m.Digitalocean.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("digitalocean") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("digitalocean") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) contextValidateGcp(ctx context.Context, formats strfmt.Registry) error { + + if m.Gcp != nil { + if err := m.Gcp.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("gcp") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("gcp") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) contextValidateHetzner(ctx context.Context, formats strfmt.Registry) error { + + if m.Hetzner != nil { + if err := m.Hetzner.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("hetzner") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("hetzner") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) contextValidatePassword(ctx context.Context, formats strfmt.Registry) error { + + if m.Password != nil { + if err := m.Password.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("password") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("password") + } + return err + } + } + + return nil +} + +func (m *RequestSecretValue) contextValidateSSHKey(ctx context.Context, formats strfmt.Registry) error { + + if m.SSHKey != nil { + if err := m.SSHKey.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("ssh_key") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("ssh_key") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValue) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValue) UnmarshalBinary(b []byte) error { + var res RequestSecretValue + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value_aws.go b/console/service/models/request_secret_value_aws.go new file mode 100644 index 000000000..209d2e969 --- /dev/null +++ b/console/service/models/request_secret_value_aws.go @@ -0,0 +1,53 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValueAws request secret value aws +// +// swagger:model Request.SecretValue.Aws +type RequestSecretValueAws struct { + + // a w s a c c e s s k e y ID + AWSACCESSKEYID string `json:"AWS_ACCESS_KEY_ID,omitempty"` + + // a w s s e c r e t a c c e s s k e y + AWSSECRETACCESSKEY string `json:"AWS_SECRET_ACCESS_KEY,omitempty"` +} + +// Validate validates this request secret value aws +func (m *RequestSecretValueAws) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret value aws based on context it is used +func (m *RequestSecretValueAws) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValueAws) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValueAws) UnmarshalBinary(b []byte) error { + var res RequestSecretValueAws + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value_azure.go b/console/service/models/request_secret_value_azure.go new file mode 100644 index 000000000..94e5feacc --- /dev/null +++ b/console/service/models/request_secret_value_azure.go @@ -0,0 +1,59 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValueAzure request secret value azure +// +// swagger:model Request.SecretValue.Azure +type RequestSecretValueAzure struct { + + // a z u r e c l i e n t ID + AZURECLIENTID string `json:"AZURE_CLIENT_ID,omitempty"` + + // a z u r e s e c r e t + AZURESECRET string `json:"AZURE_SECRET,omitempty"` + + // a z u r e s u b s c r IP t i o n ID + AZURESUBSCRIPTIONID string `json:"AZURE_SUBSCRIPTION_ID,omitempty"` + + // a z u r e t e n a n t + AZURETENANT string `json:"AZURE_TENANT,omitempty"` +} + +// Validate validates this request secret value azure +func (m *RequestSecretValueAzure) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret value azure based on context it is used +func (m *RequestSecretValueAzure) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValueAzure) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValueAzure) UnmarshalBinary(b []byte) error { + var res RequestSecretValueAzure + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value_digital_ocean.go b/console/service/models/request_secret_value_digital_ocean.go new file mode 100644 index 000000000..0588914d9 --- /dev/null +++ b/console/service/models/request_secret_value_digital_ocean.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValueDigitalOcean request secret value digital ocean +// +// swagger:model Request.SecretValue.DigitalOcean +type RequestSecretValueDigitalOcean struct { + + // d o API t o k e n + DOAPITOKEN string `json:"DO_API_TOKEN,omitempty"` +} + +// Validate validates this request secret value digital ocean +func (m *RequestSecretValueDigitalOcean) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret value digital ocean based on context it is used +func (m *RequestSecretValueDigitalOcean) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValueDigitalOcean) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValueDigitalOcean) UnmarshalBinary(b []byte) error { + var res RequestSecretValueDigitalOcean + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value_gcp.go b/console/service/models/request_secret_value_gcp.go new file mode 100644 index 000000000..c40c329ea --- /dev/null +++ b/console/service/models/request_secret_value_gcp.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValueGcp request secret value gcp +// +// swagger:model Request.SecretValue.Gcp +type RequestSecretValueGcp struct { + + // g c p s e r v i c e a c c o u n t c o n t e n t s + GCPSERVICEACCOUNTCONTENTS string `json:"GCP_SERVICE_ACCOUNT_CONTENTS,omitempty"` +} + +// Validate validates this request secret value gcp +func (m *RequestSecretValueGcp) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret value gcp based on context it is used +func (m *RequestSecretValueGcp) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValueGcp) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValueGcp) UnmarshalBinary(b []byte) error { + var res RequestSecretValueGcp + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value_hetzner.go b/console/service/models/request_secret_value_hetzner.go new file mode 100644 index 000000000..1b21d7822 --- /dev/null +++ b/console/service/models/request_secret_value_hetzner.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValueHetzner request secret value hetzner +// +// swagger:model Request.SecretValue.Hetzner +type RequestSecretValueHetzner struct { + + // h c l o u d API t o k e n + HCLOUDAPITOKEN string `json:"HCLOUD_API_TOKEN,omitempty"` +} + +// Validate validates this request secret value hetzner +func (m *RequestSecretValueHetzner) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret value hetzner based on context it is used +func (m *RequestSecretValueHetzner) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValueHetzner) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValueHetzner) UnmarshalBinary(b []byte) error { + var res RequestSecretValueHetzner + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value_password.go b/console/service/models/request_secret_value_password.go new file mode 100644 index 000000000..0d002433a --- /dev/null +++ b/console/service/models/request_secret_value_password.go @@ -0,0 +1,53 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValuePassword request secret value password +// +// swagger:model Request.SecretValue.Password +type RequestSecretValuePassword struct { + + // p a s s w o r d + PASSWORD string `json:"PASSWORD,omitempty"` + + // u s e r n a m e + USERNAME string `json:"USERNAME,omitempty"` +} + +// Validate validates this request secret value password +func (m *RequestSecretValuePassword) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret value password based on context it is used +func (m *RequestSecretValuePassword) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValuePassword) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValuePassword) UnmarshalBinary(b []byte) error { + var res RequestSecretValuePassword + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/request_secret_value_ssh_key.go b/console/service/models/request_secret_value_ssh_key.go new file mode 100644 index 000000000..627866742 --- /dev/null +++ b/console/service/models/request_secret_value_ssh_key.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// RequestSecretValueSSHKey request secret value Ssh key +// +// swagger:model Request.SecretValue.SshKey +type RequestSecretValueSSHKey struct { + + // SSH p r i v a t e k e y + SSHPRIVATEKEY string `json:"SSH_PRIVATE_KEY,omitempty"` +} + +// Validate validates this request secret value Ssh key +func (m *RequestSecretValueSSHKey) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this request secret value Ssh key based on context it is used +func (m *RequestSecretValueSSHKey) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RequestSecretValueSSHKey) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RequestSecretValueSSHKey) UnmarshalBinary(b []byte) error { + var res RequestSecretValueSSHKey + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_cluster_create.go b/console/service/models/response_cluster_create.go new file mode 100644 index 000000000..d7e282ab1 --- /dev/null +++ b/console/service/models/response_cluster_create.go @@ -0,0 +1,53 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseClusterCreate Response struct for cluster creation +// +// swagger:model Response.ClusterCreate +type ResponseClusterCreate struct { + + // unique code for cluster + ClusterID int64 `json:"cluster_id,omitempty"` + + // operation id + OperationID int64 `json:"operation_id,omitempty"` +} + +// Validate validates this response cluster create +func (m *ResponseClusterCreate) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this response cluster create based on context it is used +func (m *ResponseClusterCreate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseClusterCreate) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseClusterCreate) UnmarshalBinary(b []byte) error { + var res ResponseClusterCreate + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_cluster_default_name.go b/console/service/models/response_cluster_default_name.go new file mode 100644 index 000000000..de0a96cb4 --- /dev/null +++ b/console/service/models/response_cluster_default_name.go @@ -0,0 +1,51 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseClusterDefaultName Response struct for cluster default name +// +// swagger:model Response.ClusterDefaultName +type ResponseClusterDefaultName struct { + + // name + // Example: postgres-cluster-01 + Name string `json:"name,omitempty"` +} + +// Validate validates this response cluster default name +func (m *ResponseClusterDefaultName) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this response cluster default name based on context it is used +func (m *ResponseClusterDefaultName) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseClusterDefaultName) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseClusterDefaultName) UnmarshalBinary(b []byte) error { + var res ResponseClusterDefaultName + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_cluster_logs.go b/console/service/models/response_cluster_logs.go new file mode 100644 index 000000000..f70dfcb3e --- /dev/null +++ b/console/service/models/response_cluster_logs.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseClusterLogs Logs for cluster +// +// swagger:model Response.ClusterLogs +type ResponseClusterLogs struct { + + // all available logs + Logs string `json:"logs,omitempty"` +} + +// Validate validates this response cluster logs +func (m *ResponseClusterLogs) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this response cluster logs based on context it is used +func (m *ResponseClusterLogs) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseClusterLogs) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseClusterLogs) UnmarshalBinary(b []byte) error { + var res ResponseClusterLogs + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_clusters_info.go b/console/service/models/response_clusters_info.go new file mode 100644 index 000000000..d8a763acf --- /dev/null +++ b/console/service/models/response_clusters_info.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseClustersInfo response clusters info +// +// swagger:model Response.ClustersInfo +type ResponseClustersInfo struct { + + // data + Data []*ClusterInfo `json:"data"` + + // meta + Meta *MetaPagination `json:"meta,omitempty"` +} + +// Validate validates this response clusters info +func (m *ResponseClustersInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMeta(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseClustersInfo) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseClustersInfo) validateMeta(formats strfmt.Registry) error { + if swag.IsZero(m.Meta) { // not required + return nil + } + + if m.Meta != nil { + if err := m.Meta.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response clusters info based on the context it is used +func (m *ResponseClustersInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMeta(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseClustersInfo) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseClustersInfo) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { + + if m.Meta != nil { + if err := m.Meta.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseClustersInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseClustersInfo) UnmarshalBinary(b []byte) error { + var res ResponseClustersInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_database_extension.go b/console/service/models/response_database_extension.go new file mode 100644 index 000000000..afc392c8d --- /dev/null +++ b/console/service/models/response_database_extension.go @@ -0,0 +1,75 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseDatabaseExtension Info about database extension +// +// swagger:model Response.DatabaseExtension +type ResponseDatabaseExtension struct { + + // contrib + // Example: false + Contrib bool `json:"contrib,omitempty"` + + // description + // Example: Citus is PostgreSQL extension that transforms... + Description *string `json:"description,omitempty"` + + // image + // Example: citus.png + Image *string `json:"image,omitempty"` + + // name + // Example: Citus + Name string `json:"name,omitempty"` + + // postgres max version + // Example: 16 + PostgresMaxVersion *string `json:"postgres_max_version,omitempty"` + + // postgres min version + // Example: 11 + PostgresMinVersion *string `json:"postgres_min_version,omitempty"` + + // url + // Example: https://github.com/citusdata/citus + URL *string `json:"url,omitempty"` +} + +// Validate validates this response database extension +func (m *ResponseDatabaseExtension) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this response database extension based on context it is used +func (m *ResponseDatabaseExtension) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseDatabaseExtension) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseDatabaseExtension) UnmarshalBinary(b []byte) error { + var res ResponseDatabaseExtension + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_database_extensions.go b/console/service/models/response_database_extensions.go new file mode 100644 index 000000000..bc219171c --- /dev/null +++ b/console/service/models/response_database_extensions.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseDatabaseExtensions response database extensions +// +// swagger:model Response.DatabaseExtensions +type ResponseDatabaseExtensions struct { + + // data + Data []*ResponseDatabaseExtension `json:"data"` + + // meta + Meta *MetaPagination `json:"meta,omitempty"` +} + +// Validate validates this response database extensions +func (m *ResponseDatabaseExtensions) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMeta(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDatabaseExtensions) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDatabaseExtensions) validateMeta(formats strfmt.Registry) error { + if swag.IsZero(m.Meta) { // not required + return nil + } + + if m.Meta != nil { + if err := m.Meta.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response database extensions based on the context it is used +func (m *ResponseDatabaseExtensions) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMeta(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDatabaseExtensions) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDatabaseExtensions) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { + + if m.Meta != nil { + if err := m.Meta.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseDatabaseExtensions) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseDatabaseExtensions) UnmarshalBinary(b []byte) error { + var res ResponseDatabaseExtensions + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_deployment_info.go b/console/service/models/response_deployment_info.go new file mode 100644 index 000000000..50aea3694 --- /dev/null +++ b/console/service/models/response_deployment_info.go @@ -0,0 +1,507 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseDeploymentInfo Deployment info +// +// swagger:model Response.DeploymentInfo +type ResponseDeploymentInfo struct { + + // avatar url + AvatarURL string `json:"avatar_url,omitempty"` + + // List of available regions for current deployment + CloudRegions []*DeploymentInfoCloudRegion `json:"cloud_regions"` + + // code + // Example: aws + Code string `json:"code,omitempty"` + + // description + // Example: Amazon web services + Description string `json:"description,omitempty"` + + // instance types + InstanceTypes *ResponseDeploymentInfoInstanceTypes `json:"instance_types,omitempty"` + + // Hardware disks info + Volumes []*ResponseDeploymentInfoVolumesItems0 `json:"volumes"` +} + +// Validate validates this response deployment info +func (m *ResponseDeploymentInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCloudRegions(formats); err != nil { + res = append(res, err) + } + + if err := m.validateInstanceTypes(formats); err != nil { + res = append(res, err) + } + + if err := m.validateVolumes(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDeploymentInfo) validateCloudRegions(formats strfmt.Registry) error { + if swag.IsZero(m.CloudRegions) { // not required + return nil + } + + for i := 0; i < len(m.CloudRegions); i++ { + if swag.IsZero(m.CloudRegions[i]) { // not required + continue + } + + if m.CloudRegions[i] != nil { + if err := m.CloudRegions[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("cloud_regions" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("cloud_regions" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentInfo) validateInstanceTypes(formats strfmt.Registry) error { + if swag.IsZero(m.InstanceTypes) { // not required + return nil + } + + if m.InstanceTypes != nil { + if err := m.InstanceTypes.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types") + } + return err + } + } + + return nil +} + +func (m *ResponseDeploymentInfo) validateVolumes(formats strfmt.Registry) error { + if swag.IsZero(m.Volumes) { // not required + return nil + } + + for i := 0; i < len(m.Volumes); i++ { + if swag.IsZero(m.Volumes[i]) { // not required + continue + } + + if m.Volumes[i] != nil { + if err := m.Volumes[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("volumes" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("volumes" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this response deployment info based on the context it is used +func (m *ResponseDeploymentInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCloudRegions(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateInstanceTypes(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateVolumes(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDeploymentInfo) contextValidateCloudRegions(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.CloudRegions); i++ { + + if m.CloudRegions[i] != nil { + if err := m.CloudRegions[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("cloud_regions" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("cloud_regions" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentInfo) contextValidateInstanceTypes(ctx context.Context, formats strfmt.Registry) error { + + if m.InstanceTypes != nil { + if err := m.InstanceTypes.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types") + } + return err + } + } + + return nil +} + +func (m *ResponseDeploymentInfo) contextValidateVolumes(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Volumes); i++ { + + if m.Volumes[i] != nil { + if err := m.Volumes[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("volumes" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("volumes" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseDeploymentInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseDeploymentInfo) UnmarshalBinary(b []byte) error { + var res ResponseDeploymentInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// ResponseDeploymentInfoInstanceTypes Lists of available instance types +// +// swagger:model ResponseDeploymentInfoInstanceTypes +type ResponseDeploymentInfoInstanceTypes struct { + + // large + Large []*DeploymentInstanceType `json:"large"` + + // medium + Medium []*DeploymentInstanceType `json:"medium"` + + // small + Small []*DeploymentInstanceType `json:"small"` +} + +// Validate validates this response deployment info instance types +func (m *ResponseDeploymentInfoInstanceTypes) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateLarge(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMedium(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSmall(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDeploymentInfoInstanceTypes) validateLarge(formats strfmt.Registry) error { + if swag.IsZero(m.Large) { // not required + return nil + } + + for i := 0; i < len(m.Large); i++ { + if swag.IsZero(m.Large[i]) { // not required + continue + } + + if m.Large[i] != nil { + if err := m.Large[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types" + "." + "large" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types" + "." + "large" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentInfoInstanceTypes) validateMedium(formats strfmt.Registry) error { + if swag.IsZero(m.Medium) { // not required + return nil + } + + for i := 0; i < len(m.Medium); i++ { + if swag.IsZero(m.Medium[i]) { // not required + continue + } + + if m.Medium[i] != nil { + if err := m.Medium[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types" + "." + "medium" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types" + "." + "medium" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentInfoInstanceTypes) validateSmall(formats strfmt.Registry) error { + if swag.IsZero(m.Small) { // not required + return nil + } + + for i := 0; i < len(m.Small); i++ { + if swag.IsZero(m.Small[i]) { // not required + continue + } + + if m.Small[i] != nil { + if err := m.Small[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types" + "." + "small" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types" + "." + "small" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this response deployment info instance types based on the context it is used +func (m *ResponseDeploymentInfoInstanceTypes) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateLarge(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMedium(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSmall(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDeploymentInfoInstanceTypes) contextValidateLarge(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Large); i++ { + + if m.Large[i] != nil { + if err := m.Large[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types" + "." + "large" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types" + "." + "large" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentInfoInstanceTypes) contextValidateMedium(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Medium); i++ { + + if m.Medium[i] != nil { + if err := m.Medium[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types" + "." + "medium" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types" + "." + "medium" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentInfoInstanceTypes) contextValidateSmall(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Small); i++ { + + if m.Small[i] != nil { + if err := m.Small[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("instance_types" + "." + "small" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("instance_types" + "." + "small" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseDeploymentInfoInstanceTypes) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseDeploymentInfoInstanceTypes) UnmarshalBinary(b []byte) error { + var res ResponseDeploymentInfoInstanceTypes + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// ResponseDeploymentInfoVolumesItems0 response deployment info volumes items0 +// +// swagger:model ResponseDeploymentInfoVolumesItems0 +type ResponseDeploymentInfoVolumesItems0 struct { + + // Price currency + // Example: $ + Currency string `json:"currency,omitempty"` + + // Default volume + // Example: false + IsDefault *bool `json:"is_default,omitempty"` + + // Sets in GB + // Example: 256 + MaxSize int64 `json:"max_size,omitempty"` + + // Sets in GB + // Example: 10 + MinSize int64 `json:"min_size,omitempty"` + + // Price for disk by months + // Example: 0.1 + PriceMonthly float64 `json:"price_monthly,omitempty"` + + // Volume description + // Example: General purpose SSD disk + VolumeDescription string `json:"volume_description,omitempty"` + + // Volume type + // Example: gp3 + VolumeType string `json:"volume_type,omitempty"` +} + +// Validate validates this response deployment info volumes items0 +func (m *ResponseDeploymentInfoVolumesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this response deployment info volumes items0 based on context it is used +func (m *ResponseDeploymentInfoVolumesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseDeploymentInfoVolumesItems0) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseDeploymentInfoVolumesItems0) UnmarshalBinary(b []byte) error { + var res ResponseDeploymentInfoVolumesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_deployments_info.go b/console/service/models/response_deployments_info.go new file mode 100644 index 000000000..a15324281 --- /dev/null +++ b/console/service/models/response_deployments_info.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseDeploymentsInfo Deployments info +// +// swagger:model Response.DeploymentsInfo +type ResponseDeploymentsInfo struct { + + // data + Data []*ResponseDeploymentInfo `json:"data"` + + // meta + Meta *MetaPagination `json:"meta,omitempty"` +} + +// Validate validates this response deployments info +func (m *ResponseDeploymentsInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMeta(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDeploymentsInfo) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentsInfo) validateMeta(formats strfmt.Registry) error { + if swag.IsZero(m.Meta) { // not required + return nil + } + + if m.Meta != nil { + if err := m.Meta.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response deployments info based on the context it is used +func (m *ResponseDeploymentsInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMeta(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseDeploymentsInfo) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseDeploymentsInfo) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { + + if m.Meta != nil { + if err := m.Meta.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseDeploymentsInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseDeploymentsInfo) UnmarshalBinary(b []byte) error { + var res ResponseDeploymentsInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_environment.go b/console/service/models/response_environment.go new file mode 100644 index 000000000..f6f1e608f --- /dev/null +++ b/console/service/models/response_environment.go @@ -0,0 +1,108 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ResponseEnvironment response environment +// +// swagger:model Response.Environment +type ResponseEnvironment struct { + + // created at + // Example: 16.10.2023T11:20:00Z + // Format: date-time + CreatedAt strfmt.DateTime `json:"created_at,omitempty"` + + // description + // Example: environment for production + Description *string `json:"description,omitempty"` + + // id + // Example: 1 + ID int64 `json:"id,omitempty"` + + // name + // Example: production + Name string `json:"name,omitempty"` + + // updated at + // Example: 16.10.2023T11:20:00Z + // Format: date-time + UpdatedAt *strfmt.DateTime `json:"updated_at,omitempty"` +} + +// Validate validates this response environment +func (m *ResponseEnvironment) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreatedAt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseEnvironment) validateCreatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.CreatedAt) { // not required + return nil + } + + if err := validate.FormatOf("created_at", "body", "date-time", m.CreatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *ResponseEnvironment) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updated_at", "body", "date-time", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this response environment based on context it is used +func (m *ResponseEnvironment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseEnvironment) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseEnvironment) UnmarshalBinary(b []byte) error { + var res ResponseEnvironment + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_environments_list.go b/console/service/models/response_environments_list.go new file mode 100644 index 000000000..534e55e8a --- /dev/null +++ b/console/service/models/response_environments_list.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseEnvironmentsList response environments list +// +// swagger:model Response.EnvironmentsList +type ResponseEnvironmentsList struct { + + // data + Data []*ResponseEnvironment `json:"data"` + + // meta + Meta *MetaPagination `json:"meta,omitempty"` +} + +// Validate validates this response environments list +func (m *ResponseEnvironmentsList) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMeta(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseEnvironmentsList) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseEnvironmentsList) validateMeta(formats strfmt.Registry) error { + if swag.IsZero(m.Meta) { // not required + return nil + } + + if m.Meta != nil { + if err := m.Meta.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response environments list based on the context it is used +func (m *ResponseEnvironmentsList) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMeta(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseEnvironmentsList) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseEnvironmentsList) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { + + if m.Meta != nil { + if err := m.Meta.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseEnvironmentsList) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseEnvironmentsList) UnmarshalBinary(b []byte) error { + var res ResponseEnvironmentsList + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_error.go b/console/service/models/response_error.go new file mode 100644 index 000000000..889da4426 --- /dev/null +++ b/console/service/models/response_error.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseError Error object +// +// swagger:model Response.Error +type ResponseError struct { + + // code + Code int64 `json:"code,omitempty"` + + // description + Description string `json:"description,omitempty"` + + // title + Title string `json:"title,omitempty"` +} + +// Validate validates this response error +func (m *ResponseError) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this response error based on context it is used +func (m *ResponseError) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseError) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseError) UnmarshalBinary(b []byte) error { + var res ResponseError + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_operation.go b/console/service/models/response_operation.go new file mode 100644 index 000000000..891183da6 --- /dev/null +++ b/console/service/models/response_operation.go @@ -0,0 +1,116 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ResponseOperation response operation +// +// swagger:model Response.Operation +type ResponseOperation struct { + + // cluster name + // Example: drm-prod-cluster + ClusterName string `json:"cluster_name,omitempty"` + + // environment + // Example: production + Environment string `json:"environment,omitempty"` + + // finished + // Example: 16.10.2023T11:20:00Z + // Format: date-time + Finished *strfmt.DateTime `json:"finished,omitempty"` + + // id + // Example: 1 + ID int64 `json:"id,omitempty"` + + // started + // Example: 16.10.2023T11:20:00Z + // Format: date-time + Started strfmt.DateTime `json:"started,omitempty"` + + // status + // Example: success + Status string `json:"status,omitempty"` + + // type + // Example: deploy + Type string `json:"type,omitempty"` +} + +// Validate validates this response operation +func (m *ResponseOperation) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateFinished(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStarted(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseOperation) validateFinished(formats strfmt.Registry) error { + if swag.IsZero(m.Finished) { // not required + return nil + } + + if err := validate.FormatOf("finished", "body", "date-time", m.Finished.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *ResponseOperation) validateStarted(formats strfmt.Registry) error { + if swag.IsZero(m.Started) { // not required + return nil + } + + if err := validate.FormatOf("started", "body", "date-time", m.Started.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this response operation based on context it is used +func (m *ResponseOperation) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseOperation) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseOperation) UnmarshalBinary(b []byte) error { + var res ResponseOperation + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_operations_list.go b/console/service/models/response_operations_list.go new file mode 100644 index 000000000..22c0a6d78 --- /dev/null +++ b/console/service/models/response_operations_list.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseOperationsList response operations list +// +// swagger:model Response.OperationsList +type ResponseOperationsList struct { + + // data + Data []*ResponseOperation `json:"data"` + + // meta + Meta *MetaPagination `json:"meta,omitempty"` +} + +// Validate validates this response operations list +func (m *ResponseOperationsList) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMeta(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseOperationsList) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseOperationsList) validateMeta(formats strfmt.Registry) error { + if swag.IsZero(m.Meta) { // not required + return nil + } + + if m.Meta != nil { + if err := m.Meta.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response operations list based on the context it is used +func (m *ResponseOperationsList) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMeta(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseOperationsList) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseOperationsList) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { + + if m.Meta != nil { + if err := m.Meta.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseOperationsList) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseOperationsList) UnmarshalBinary(b []byte) error { + var res ResponseOperationsList + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_postgres_version.go b/console/service/models/response_postgres_version.go new file mode 100644 index 000000000..b899231f3 --- /dev/null +++ b/console/service/models/response_postgres_version.go @@ -0,0 +1,100 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ResponsePostgresVersion response postgres version +// +// swagger:model Response.PostgresVersion +type ResponsePostgresVersion struct { + + // end of life + // Example: 2022-11-10 + // Format: date + EndOfLife strfmt.Date `json:"end_of_life,omitempty"` + + // major version + // Example: 10 + MajorVersion int64 `json:"major_version,omitempty"` + + // release date + // Example: 2017-10-05 + // Format: date + ReleaseDate strfmt.Date `json:"release_date,omitempty"` +} + +// Validate validates this response postgres version +func (m *ResponsePostgresVersion) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateEndOfLife(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReleaseDate(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponsePostgresVersion) validateEndOfLife(formats strfmt.Registry) error { + if swag.IsZero(m.EndOfLife) { // not required + return nil + } + + if err := validate.FormatOf("end_of_life", "body", "date", m.EndOfLife.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *ResponsePostgresVersion) validateReleaseDate(formats strfmt.Registry) error { + if swag.IsZero(m.ReleaseDate) { // not required + return nil + } + + if err := validate.FormatOf("release_date", "body", "date", m.ReleaseDate.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this response postgres version based on context it is used +func (m *ResponsePostgresVersion) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponsePostgresVersion) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponsePostgresVersion) UnmarshalBinary(b []byte) error { + var res ResponsePostgresVersion + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_postgres_versions.go b/console/service/models/response_postgres_versions.go new file mode 100644 index 000000000..05d70dea8 --- /dev/null +++ b/console/service/models/response_postgres_versions.go @@ -0,0 +1,116 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponsePostgresVersions response postgres versions +// +// swagger:model Response.PostgresVersions +type ResponsePostgresVersions struct { + + // data + Data []*ResponsePostgresVersion `json:"data"` +} + +// Validate validates this response postgres versions +func (m *ResponsePostgresVersions) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponsePostgresVersions) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this response postgres versions based on the context it is used +func (m *ResponsePostgresVersions) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponsePostgresVersions) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponsePostgresVersions) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponsePostgresVersions) UnmarshalBinary(b []byte) error { + var res ResponsePostgresVersions + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_project.go b/console/service/models/response_project.go new file mode 100644 index 000000000..d26fb373e --- /dev/null +++ b/console/service/models/response_project.go @@ -0,0 +1,105 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ResponseProject response project +// +// swagger:model Response.Project +type ResponseProject struct { + + // created at + // Example: 16.10.2023T11:20:00Z + // Format: date-time + CreatedAt strfmt.DateTime `json:"created_at,omitempty"` + + // description + Description *string `json:"description,omitempty"` + + // id + ID int64 `json:"id,omitempty"` + + // name + Name string `json:"name,omitempty"` + + // updated at + // Example: 16.10.2023T11:20:00Z + // Format: date-time + UpdatedAt *strfmt.DateTime `json:"updated_at,omitempty"` +} + +// Validate validates this response project +func (m *ResponseProject) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreatedAt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseProject) validateCreatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.CreatedAt) { // not required + return nil + } + + if err := validate.FormatOf("created_at", "body", "date-time", m.CreatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *ResponseProject) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updated_at", "body", "date-time", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this response project based on context it is used +func (m *ResponseProject) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseProject) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseProject) UnmarshalBinary(b []byte) error { + var res ResponseProject + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_projects_list.go b/console/service/models/response_projects_list.go new file mode 100644 index 000000000..3d7bbe286 --- /dev/null +++ b/console/service/models/response_projects_list.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseProjectsList response projects list +// +// swagger:model Response.ProjectsList +type ResponseProjectsList struct { + + // data + Data []*ResponseProject `json:"data"` + + // meta + Meta *MetaPagination `json:"meta,omitempty"` +} + +// Validate validates this response projects list +func (m *ResponseProjectsList) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMeta(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseProjectsList) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseProjectsList) validateMeta(formats strfmt.Registry) error { + if swag.IsZero(m.Meta) { // not required + return nil + } + + if m.Meta != nil { + if err := m.Meta.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response projects list based on the context it is used +func (m *ResponseProjectsList) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMeta(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseProjectsList) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseProjectsList) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { + + if m.Meta != nil { + if err := m.Meta.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseProjectsList) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseProjectsList) UnmarshalBinary(b []byte) error { + var res ResponseProjectsList + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_secret_info.go b/console/service/models/response_secret_info.go new file mode 100644 index 000000000..9d32c66f7 --- /dev/null +++ b/console/service/models/response_secret_info.go @@ -0,0 +1,163 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ResponseSecretInfo response secret info +// +// swagger:model Response.SecretInfo +type ResponseSecretInfo struct { + + // created at + // Example: 16.10.2023T11:20:00Z + // Format: date-time + CreatedAt strfmt.DateTime `json:"created_at,omitempty"` + + // id + // Example: 1 + ID int64 `json:"id,omitempty"` + + // is used + // Example: true + IsUsed bool `json:"is_used,omitempty"` + + // name + // Example: aws key + Name string `json:"name,omitempty"` + + // project id + // Example: 1 + ProjectID int64 `json:"project_id,omitempty"` + + // type + Type SecretType `json:"type,omitempty"` + + // updated at + // Example: 16.10.2023T11:20:00Z + // Format: date-time + UpdatedAt *strfmt.DateTime `json:"updated_at,omitempty"` + + // used by clusters + // Example: mds-prod, drm-prod + UsedByClusters *string `json:"used_by_clusters,omitempty"` +} + +// Validate validates this response secret info +func (m *ResponseSecretInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreatedAt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateType(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseSecretInfo) validateCreatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.CreatedAt) { // not required + return nil + } + + if err := validate.FormatOf("created_at", "body", "date-time", m.CreatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *ResponseSecretInfo) validateType(formats strfmt.Registry) error { + if swag.IsZero(m.Type) { // not required + return nil + } + + if err := m.Type.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("type") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("type") + } + return err + } + + return nil +} + +func (m *ResponseSecretInfo) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updated_at", "body", "date-time", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this response secret info based on the context it is used +func (m *ResponseSecretInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateType(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseSecretInfo) contextValidateType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.Type.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("type") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("type") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseSecretInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseSecretInfo) UnmarshalBinary(b []byte) error { + var res ResponseSecretInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_secret_info_list.go b/console/service/models/response_secret_info_list.go new file mode 100644 index 000000000..cb76291f8 --- /dev/null +++ b/console/service/models/response_secret_info_list.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseSecretInfoList response secret info list +// +// swagger:model Response.SecretInfoList +type ResponseSecretInfoList struct { + + // data + Data []*ResponseSecretInfo `json:"data"` + + // meta + Meta *MetaPagination `json:"meta,omitempty"` +} + +// Validate validates this response secret info list +func (m *ResponseSecretInfoList) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMeta(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseSecretInfoList) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseSecretInfoList) validateMeta(formats strfmt.Registry) error { + if swag.IsZero(m.Meta) { // not required + return nil + } + + if m.Meta != nil { + if err := m.Meta.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response secret info list based on the context it is used +func (m *ResponseSecretInfoList) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMeta(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseSecretInfoList) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseSecretInfoList) contextValidateMeta(ctx context.Context, formats strfmt.Registry) error { + + if m.Meta != nil { + if err := m.Meta.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("meta") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("meta") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseSecretInfoList) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseSecretInfoList) UnmarshalBinary(b []byte) error { + var res ResponseSecretInfoList + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_setting.go b/console/service/models/response_setting.go new file mode 100644 index 000000000..4fa199e87 --- /dev/null +++ b/console/service/models/response_setting.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ResponseSetting Setting +// +// swagger:model Response.Setting +type ResponseSetting struct { + + // created at + // Format: datetime + CreatedAt strfmt.DateTime `json:"created_at,omitempty"` + + // id + ID int64 `json:"id,omitempty"` + + // name + Name string `json:"name,omitempty"` + + // updated at + // Format: datetime + UpdatedAt *strfmt.DateTime `json:"updated_at,omitempty"` + + // value + Value interface{} `json:"value,omitempty"` +} + +// Validate validates this response setting +func (m *ResponseSetting) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreatedAt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseSetting) validateCreatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.CreatedAt) { // not required + return nil + } + + if err := validate.FormatOf("created_at", "body", "datetime", m.CreatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *ResponseSetting) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updated_at", "body", "datetime", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this response setting based on context it is used +func (m *ResponseSetting) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseSetting) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseSetting) UnmarshalBinary(b []byte) error { + var res ResponseSetting + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_settings.go b/console/service/models/response_settings.go new file mode 100644 index 000000000..e9eaf2d60 --- /dev/null +++ b/console/service/models/response_settings.go @@ -0,0 +1,162 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseSettings List of settings +// +// swagger:model Response.Settings +type ResponseSettings struct { + + // data + Data []*ResponseSetting `json:"data"` + + // mete + Mete *MetaPagination `json:"mete,omitempty"` +} + +// Validate validates this response settings +func (m *ResponseSettings) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateData(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMete(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseSettings) validateData(formats strfmt.Registry) error { + if swag.IsZero(m.Data) { // not required + return nil + } + + for i := 0; i < len(m.Data); i++ { + if swag.IsZero(m.Data[i]) { // not required + continue + } + + if m.Data[i] != nil { + if err := m.Data[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseSettings) validateMete(formats strfmt.Registry) error { + if swag.IsZero(m.Mete) { // not required + return nil + } + + if m.Mete != nil { + if err := m.Mete.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("mete") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("mete") + } + return err + } + } + + return nil +} + +// ContextValidate validate this response settings based on the context it is used +func (m *ResponseSettings) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateData(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateMete(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ResponseSettings) contextValidateData(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Data); i++ { + + if m.Data[i] != nil { + if err := m.Data[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("data" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("data" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *ResponseSettings) contextValidateMete(ctx context.Context, formats strfmt.Registry) error { + + if m.Mete != nil { + if err := m.Mete.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("mete") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("mete") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseSettings) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseSettings) UnmarshalBinary(b []byte) error { + var res ResponseSettings + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/response_version.go b/console/service/models/response_version.go new file mode 100644 index 000000000..3b8015716 --- /dev/null +++ b/console/service/models/response_version.go @@ -0,0 +1,51 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResponseVersion Version response +// +// swagger:model Response.Version +type ResponseVersion struct { + + // version + // Example: v1.0.0 + Version string `json:"version,omitempty"` +} + +// Validate validates this response version +func (m *ResponseVersion) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this response version based on context it is used +func (m *ResponseVersion) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResponseVersion) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResponseVersion) UnmarshalBinary(b []byte) error { + var res ResponseVersion + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/console/service/models/secret_type.go b/console/service/models/secret_type.go new file mode 100644 index 000000000..0152925cb --- /dev/null +++ b/console/service/models/secret_type.go @@ -0,0 +1,93 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// SecretType secret type +// +// swagger:model Secret.Type +type SecretType string + +func NewSecretType(value SecretType) *SecretType { + return &value +} + +// Pointer returns a pointer to a freshly-allocated SecretType. +func (m SecretType) Pointer() *SecretType { + return &m +} + +const ( + + // SecretTypeAws captures enum value "aws" + SecretTypeAws SecretType = "aws" + + // SecretTypeGcp captures enum value "gcp" + SecretTypeGcp SecretType = "gcp" + + // SecretTypeHetzner captures enum value "hetzner" + SecretTypeHetzner SecretType = "hetzner" + + // SecretTypeSSHKey captures enum value "ssh_key" + SecretTypeSSHKey SecretType = "ssh_key" + + // SecretTypeDigitalocean captures enum value "digitalocean" + SecretTypeDigitalocean SecretType = "digitalocean" + + // SecretTypePassword captures enum value "password" + SecretTypePassword SecretType = "password" + + // SecretTypeAzure captures enum value "azure" + SecretTypeAzure SecretType = "azure" +) + +// for schema +var secretTypeEnum []interface{} + +func init() { + var res []SecretType + if err := json.Unmarshal([]byte(`["aws","gcp","hetzner","ssh_key","digitalocean","password","azure"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + secretTypeEnum = append(secretTypeEnum, v) + } +} + +func (m SecretType) validateSecretTypeEnum(path, location string, value SecretType) error { + if err := validate.EnumCase(path, location, value, secretTypeEnum, true); err != nil { + return err + } + return nil +} + +// Validate validates this secret type +func (m SecretType) Validate(formats strfmt.Registry) error { + var res []error + + // value enum + if err := m.validateSecretTypeEnum("", "body", m); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// ContextValidate validates this secret type based on context it is used +func (m SecretType) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} diff --git a/console/service/pkg/patroni/client.go b/console/service/pkg/patroni/client.go new file mode 100644 index 000000000..a5bd5b2cc --- /dev/null +++ b/console/service/pkg/patroni/client.go @@ -0,0 +1,102 @@ +package patroni + +import ( + "context" + "encoding/json" + "github.com/rs/zerolog" + "io" + "net/http" + "postgesql-cluster-console/pkg/tracer" + "time" +) + +type IClient interface { + GetMonitoringInfo(ctx context.Context, host string) (*MonitoringInfo, error) + GetClusterInfo(ctx context.Context, host string) (*ClusterInfo, error) +} + +type pClient struct { + log zerolog.Logger + httpClient *http.Client +} + +func NewClient(log zerolog.Logger) IClient { + return pClient{ + log: log, + httpClient: &http.Client{ + Timeout: time.Second, + }, + } +} + +func (c pClient) GetMonitoringInfo(ctx context.Context, host string) (*MonitoringInfo, error) { + cid := ctx.Value(tracer.CtxCidKey{}).(string) + localLog := c.log.With().Str("cid", cid).Logger() + url := "http://" + host + ":8008/patroni" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + localLog.Trace().Str("request", "GET "+url).Msg("call request") + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer func() { + derr := resp.Body.Close() + if derr != nil { + localLog.Error().Err(derr).Msg("failed to close body") + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + localLog.Trace().Str("response", string(body)).Msg("got response") + + var monitoringInfo MonitoringInfo + err = json.Unmarshal(body, &monitoringInfo) + if err != nil { + return nil, err + } + + return &monitoringInfo, nil +} + +func (c pClient) GetClusterInfo(ctx context.Context, host string) (*ClusterInfo, error) { + cid := ctx.Value(tracer.CtxCidKey{}).(string) + localLog := c.log.With().Str("cid", cid).Logger() + url := "http://" + host + ":8008/cluster" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + localLog.Trace().Str("request", "GET "+url).Msg("call request") + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer func() { + derr := resp.Body.Close() + if derr != nil { + localLog.Error().Err(derr).Msg("failed to close body") + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + localLog.Trace().Str("response", string(body)).Msg("got response") + + var clusterInfo ClusterInfo + err = json.Unmarshal(body, &clusterInfo) + if err != nil { + return nil, err + } + + return &clusterInfo, nil +} diff --git a/console/service/pkg/patroni/models.go b/console/service/pkg/patroni/models.go new file mode 100644 index 000000000..e06fb8057 --- /dev/null +++ b/console/service/pkg/patroni/models.go @@ -0,0 +1,20 @@ +package patroni + +type MonitoringInfo struct { + State string `json:"state"` + Role string `json:"role"` + ServerVersion int `json:"server_version"` +} + +type ClusterInfo struct { + Members []struct { + Name string `json:"name"` + Role string `json:"role"` + State string `json:"state"` + Host string `json:"host"` + Timeline int64 `json:"timeline"` + Lag interface{} `json:"lag"` + Tags interface{} `json:"tags"` + PendingRestart bool `json:"pending_restart"` + } `json:"members"` +} diff --git a/console/service/pkg/tracer/cid.go b/console/service/pkg/tracer/cid.go new file mode 100644 index 000000000..d364d0c3a --- /dev/null +++ b/console/service/pkg/tracer/cid.go @@ -0,0 +1,3 @@ +package tracer + +type CtxCidKey struct{} diff --git a/console/service/restapi/configure_pg_console.go b/console/service/restapi/configure_pg_console.go new file mode 100644 index 000000000..ddffbe6e6 --- /dev/null +++ b/console/service/restapi/configure_pg_console.go @@ -0,0 +1,131 @@ +// This file is safe to edit. Once it exists it will not be overwritten + +package restapi + +import ( + "crypto/tls" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + + localmid "postgesql-cluster-console/middleware" + "postgesql-cluster-console/restapi/operations" + "postgesql-cluster-console/restapi/operations/cluster" + "postgesql-cluster-console/restapi/operations/dictionary" + "postgesql-cluster-console/restapi/operations/system" +) + +//go:generate swagger generate server --target ../../pg_console --name PgConsole --spec ../api/swagger.yaml --principal interface{} --exclude-main + +func configureFlags(api *operations.PgConsoleAPI) { + // api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... } +} + +func configureAPI(api *operations.PgConsoleAPI) http.Handler { + // configure the api here + api.ServeError = errors.ServeError + + // Set your custom logger if needed. Default one is log.Printf + // Expected interface func(string, ...interface{}) + // + // Example: + // api.Logger = log.Printf + + api.UseSwaggerUI() + // To continue using redoc as your UI, uncomment the following line + // api.UseRedoc() + + api.JSONConsumer = runtime.JSONConsumer() + + api.JSONProducer = runtime.JSONProducer() + + if api.ClusterGetClustersHandler == nil { + api.ClusterGetClustersHandler = cluster.GetClustersHandlerFunc(func(params cluster.GetClustersParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.GetClusters has not yet been implemented") + }) + } + if api.ClusterGetClustersIDHandler == nil { + api.ClusterGetClustersIDHandler = cluster.GetClustersIDHandlerFunc(func(params cluster.GetClustersIDParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.GetClustersID has not yet been implemented") + }) + } + if api.DictionaryGetDatabaseExtensionsHandler == nil { + api.DictionaryGetDatabaseExtensionsHandler = dictionary.GetDatabaseExtensionsHandlerFunc(func(params dictionary.GetDatabaseExtensionsParams) middleware.Responder { + return middleware.NotImplemented("operation dictionary.GetDatabaseExtensions has not yet been implemented") + }) + } + if api.DictionaryGetExternalDeploymentsHandler == nil { + api.DictionaryGetExternalDeploymentsHandler = dictionary.GetExternalDeploymentsHandlerFunc(func(params dictionary.GetExternalDeploymentsParams) middleware.Responder { + return middleware.NotImplemented("operation dictionary.GetExternalDeployments has not yet been implemented") + }) + } + if api.SystemGetVersionHandler == nil { + api.SystemGetVersionHandler = system.GetVersionHandlerFunc(func(params system.GetVersionParams) middleware.Responder { + return middleware.NotImplemented("operation system.GetVersion has not yet been implemented") + }) + } + if api.ClusterPostClustersHandler == nil { + api.ClusterPostClustersHandler = cluster.PostClustersHandlerFunc(func(params cluster.PostClustersParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClusters has not yet been implemented") + }) + } + if api.ClusterPostClustersIDReinitHandler == nil { + api.ClusterPostClustersIDReinitHandler = cluster.PostClustersIDReinitHandlerFunc(func(params cluster.PostClustersIDReinitParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDReinit has not yet been implemented") + }) + } + if api.ClusterPostClustersIDReloadHandler == nil { + api.ClusterPostClustersIDReloadHandler = cluster.PostClustersIDReloadHandlerFunc(func(params cluster.PostClustersIDReloadParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDReload has not yet been implemented") + }) + } + if api.ClusterPostClustersIDRestartHandler == nil { + api.ClusterPostClustersIDRestartHandler = cluster.PostClustersIDRestartHandlerFunc(func(params cluster.PostClustersIDRestartParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDRestart has not yet been implemented") + }) + } + if api.ClusterPostClustersIDStartHandler == nil { + api.ClusterPostClustersIDStartHandler = cluster.PostClustersIDStartHandlerFunc(func(params cluster.PostClustersIDStartParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDStart has not yet been implemented") + }) + } + if api.ClusterPostClustersIDStopHandler == nil { + api.ClusterPostClustersIDStopHandler = cluster.PostClustersIDStopHandlerFunc(func(params cluster.PostClustersIDStopParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDStop has not yet been implemented") + }) + } + + api.PreServerShutdown = func() {} + + api.ServerShutdown = func() {} + + return setupGlobalMiddleware(api.Serve(setupMiddlewares)) +} + +// The TLS configuration before HTTPS server starts. +func configureTLS(tlsConfig *tls.Config) { + // Make all necessary changes to the TLS configuration here. +} + +// As soon as server is initialized but not run yet, this function will be called. +// If you need to modify a config, store server instance to stop it individually later, this is the place. +// This function can be called multiple times, depending on the number of serving schemes. +// scheme value will be set accordingly: "http", "https" or "unix". +func configureServer(s *http.Server, scheme, addr string) { +} + +// The middleware configuration is for the handler executors. These do not apply to the swagger.json document. +// The middleware executes after routing but before authentication, binding and validation. +func setupMiddlewares(handler http.Handler) http.Handler { + return handler +} + +var Token string + +// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document. +// So this is a good place to plug in a panic handling middleware, logging and metrics. +func setupGlobalMiddleware(handler http.Handler) http.Handler { + return localmid.SetCorrelationId(localmid.CORS(localmid.RequestZeroLog(localmid.Authorization(Token, handler)))) +} diff --git a/console/service/restapi/doc.go b/console/service/restapi/doc.go new file mode 100644 index 000000000..bdea8ec17 --- /dev/null +++ b/console/service/restapi/doc.go @@ -0,0 +1,20 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Package restapi PG Console +// +// API for PG Console WEB +// Schemes: +// http +// Host: localhost:8080 +// BasePath: /api/v1 +// Version: 1.0.0 +// +// Consumes: +// - application/json +// - plain/text +// +// Produces: +// - application/json +// +// swagger:meta +package restapi diff --git a/console/service/restapi/embedded_spec.go b/console/service/restapi/embedded_spec.go new file mode 100644 index 000000000..bbd95aa41 --- /dev/null +++ b/console/service/restapi/embedded_spec.go @@ -0,0 +1,4563 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package restapi + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" +) + +var ( + // SwaggerJSON embedded version of the swagger document used at generation time + SwaggerJSON json.RawMessage + // FlatSwaggerJSON embedded flattened version of the swagger document used at generation time + FlatSwaggerJSON json.RawMessage +) + +func init() { + SwaggerJSON = json.RawMessage([]byte(`{ + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "swagger": "2.0", + "info": { + "description": "API for PG Console WEB", + "title": "PG Console", + "version": "1.0.0" + }, + "host": "localhost:8080", + "basePath": "/api/v1", + "paths": { + "/clusters": { + "get": { + "tags": [ + "cluster" + ], + "summary": "Get info about clusters", + "parameters": [ + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "project_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Filter by name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Filter by location", + "name": "location", + "in": "query" + }, + { + "type": "string", + "description": "Filter by environment", + "name": "environment", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by server_count", + "name": "server_count", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by postgres_version", + "name": "postgres_version", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Created at after this date", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Created at till this date", + "name": "created_at_to", + "in": "query" + }, + { + "type": "string", + "description": "Sort by fields. Example: sort_by=id,-name,created_at,updated_at\n Supported values:\n - id\n - name\n - created_at\n - updated_at\n - environment\n - project\n - status\n - location\n - server_count\n - postgres_version\n", + "name": "sort_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClustersInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "cluster" + ], + "summary": "Create new cluster", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/default_name": { + "get": { + "tags": [ + "cluster" + ], + "summary": "Get cluster default name", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterDefaultName" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}": { + "get": { + "tags": [ + "cluster" + ], + "summary": "Get cluster info", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ClusterInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "delete": { + "tags": [ + "cluster" + ], + "summary": "Delete cluster", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/refresh": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Refresh cluster info", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ClusterInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/reinit": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Reinit cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterReinit" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/reload": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Reload cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterReload" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/remove": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Remove cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterRemove" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/restart": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Restart cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterRestart" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/start": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Start cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterStart" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/stop": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Stop cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterStop" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/database/extensions": { + "get": { + "tags": [ + "dictionary" + ], + "summary": "Info about available database extensions", + "parameters": [ + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "enum": [ + "all", + "contrib", + "third_party" + ], + "type": "string", + "default": "all", + "name": "extension_type", + "in": "query" + }, + { + "type": "string", + "name": "postgres_version", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.DatabaseExtensions" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/environments": { + "get": { + "tags": [ + "environment" + ], + "summary": "Get environemtns list", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.EnvironmentsList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "environment" + ], + "summary": "Create environemtn", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.Environment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Environment" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/environments/{id}": { + "delete": { + "tags": [ + "environment" + ], + "summary": "Delete environment", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/external/deployments": { + "get": { + "tags": [ + "dictionary" + ], + "summary": "Get full info about available external deployments", + "parameters": [ + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.DeploymentsInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/operations": { + "get": { + "tags": [ + "operation" + ], + "summary": "Get operations list for current project", + "parameters": [ + { + "type": "integer", + "description": "Required parameter for filter", + "name": "project_id", + "in": "query", + "required": true + }, + { + "type": "string", + "format": "date-time", + "description": "Operations started after this date", + "name": "start_date", + "in": "query", + "required": true + }, + { + "type": "string", + "format": "date-time", + "description": "Operations started till this date", + "name": "end_date", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Filter by cluster_name", + "name": "cluster_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by type", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Filter by environment", + "name": "environment", + "in": "query" + }, + { + "type": "string", + "description": "Sort by fields. Example: sort_by=cluster_name,-type,status,id,created_at,updated_at\n Supported valuese:\n - id\n - cluster_name\n - type\n - status\n - started_at\n - updated_at\n - cluster\n - environment\n", + "name": "sort_by", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.OperationsList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/operations/{id}/log": { + "get": { + "consumes": [ + "plain/text" + ], + "tags": [ + "operation" + ], + "summary": "Get operation log by operation_id", + "parameters": [ + { + "type": "integer", + "description": "Operation id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + }, + "headers": { + "content-type": { + "type": "string" + }, + "x-log-completed": { + "type": "boolean" + } + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/postgres_versions": { + "get": { + "tags": [ + "dictionary" + ], + "summary": "Get supported postgres versions", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.PostgresVersions" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/projects": { + "get": { + "tags": [ + "project" + ], + "summary": "Get projects list", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ProjectsList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "project" + ], + "summary": "Create new project", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ProjectCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Project" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/projects/{id}": { + "delete": { + "tags": [ + "project" + ], + "summary": "Delete project", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "patch": { + "tags": [ + "project" + ], + "summary": "Change project", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ProjectPatch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Project" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/secrets": { + "get": { + "tags": [ + "secret" + ], + "summary": "Get secrets list", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "project_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Filter by name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by type", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Sort by fields. Example: sort_by=id,name,-type,created_at,updated_at", + "name": "sort_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.SecretInfoList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "secret" + ], + "summary": "Create new secret", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.SecretCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.SecretInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/secrets/{id}": { + "delete": { + "tags": [ + "secret" + ], + "summary": "Delete secret", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "patch": { + "tags": [ + "secret" + ], + "summary": "Change secret", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.SecretPatch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.SecretInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/servers/{id}": { + "delete": { + "tags": [ + "cluster" + ], + "summary": "Delete server from cluster", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK", + "headers": { + "x-cluster-id": { + "type": "integer" + } + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/settings": { + "get": { + "tags": [ + "setting" + ], + "summary": "Get settings", + "parameters": [ + { + "type": "string", + "description": "Filter by name", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Settings" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "setting" + ], + "summary": "Create new setting", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.CreateSetting" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Setting" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/settings/{name}": { + "patch": { + "tags": [ + "setting" + ], + "summary": "Changed setting", + "parameters": [ + { + "type": "string", + "name": "name", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ChangeSetting" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Setting" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/version": { + "get": { + "tags": [ + "system" + ], + "summary": "Get version of server", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Version" + } + } + } + } + } + }, + "definitions": { + "ClusterInfo": { + "description": "Cluster info", + "type": "object", + "properties": { + "cluster_location": { + "description": "Code of location", + "type": "string", + "example": "eu-north-1" + }, + "connection_info": { + "type": "object" + }, + "creation_time": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "description": { + "type": "string" + }, + "environment": { + "type": "string", + "example": "production" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "example": "drm-prod-pgcluster" + }, + "postgres_version": { + "type": "integer", + "format": "int32", + "example": 15 + }, + "project_name": { + "description": "Project for cluster", + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/ClusterInfo.Instance" + } + }, + "status": { + "type": "string", + "example": "healthy" + } + } + }, + "ClusterInfo.AdditionalSettings": { + "description": "Additional settings for cluster", + "type": "object", + "properties": { + "connection_info": { + "type": "object" + } + } + }, + "ClusterInfo.Instance": { + "description": "Instance info for current cluster", + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "ip": { + "type": "string", + "example": "10.128.64.141" + }, + "lag": { + "type": "integer", + "format": "int64", + "x-nullable": true, + "example": 0 + }, + "name": { + "type": "string", + "example": "pgnode1" + }, + "pending_restart": { + "type": "boolean", + "x-nullable": true, + "example": false + }, + "role": { + "type": "string", + "example": "leader" + }, + "status": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "timeline": { + "type": "integer", + "format": "int64", + "x-nullable": true, + "example": 1 + } + } + }, + "Deployment.CloudImage": { + "type": "object", + "properties": { + "arch": { + "type": "string", + "example": "amd64" + }, + "image": { + "type": "object", + "example": "{\"server_image\": \"ami-078b3985bbc361448\"}" + }, + "os_name": { + "type": "string", + "example": "Ubuntu" + }, + "os_version": { + "type": "string", + "example": "22.04 LTS" + }, + "updated_at": { + "type": "string", + "format": "datetime" + } + } + }, + "Deployment.InstanceType": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "m5.2xlarge" + }, + "cpu": { + "type": "integer", + "example": 8 + }, + "currency": { + "description": "Price currency", + "type": "string", + "example": "$" + }, + "price_hourly": { + "description": "Price for 1 instance by hour", + "type": "number", + "example": 0.01 + }, + "price_monthly": { + "description": "Price for 1 instance by month", + "type": "number", + "example": 1.2 + }, + "ram": { + "type": "integer", + "example": 256 + } + } + }, + "DeploymentInfo.CloudRegion": { + "type": "object", + "properties": { + "code": { + "description": "unique parameter for DB", + "type": "string", + "example": "north_america" + }, + "datacenters": { + "description": "List of datacenters for this region", + "type": "array", + "items": { + "type": "object", + "properties": { + "cloud_image": { + "$ref": "#/definitions/Deployment.CloudImage" + }, + "code": { + "type": "string", + "example": "ca-central-1" + }, + "location": { + "type": "string", + "example": "Canada (central)" + } + } + } + }, + "name": { + "description": "Field for web", + "type": "string", + "example": "North America" + } + } + }, + "Meta.Pagination": { + "type": "object", + "title": "Pagination info for list requests", + "properties": { + "count": { + "type": "integer", + "x-nullable": true + }, + "limit": { + "type": "integer", + "x-nullable": true + }, + "offset": { + "type": "integer", + "x-nullable": true + } + } + }, + "Request.ChangeSetting": { + "description": "Change setting", + "type": "object", + "properties": { + "value": { + "type": "object", + "x-nullable": true + } + } + }, + "Request.ClusterCreate": { + "description": "Request struct for cluster creation", + "type": "object", + "properties": { + "auth_info": { + "description": "Info for deployment system authorization", + "type": "object", + "properties": { + "secret_id": { + "type": "integer", + "example": 1 + } + } + }, + "description": { + "description": "Info about cluster", + "type": "string" + }, + "environment_id": { + "description": "Project environment", + "type": "integer" + }, + "envs": { + "type": "array", + "items": { + "type": "string" + } + }, + "extra_vars": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "example": "drm-prod-pgcluster" + }, + "project_id": { + "description": "Project for new cluster", + "type": "integer" + } + } + }, + "Request.ClusterReinit": { + "description": "Reinit cluster", + "type": "object" + }, + "Request.ClusterReload": { + "description": "Reload cluster", + "type": "object" + }, + "Request.ClusterRemove": { + "description": "Remove cluster", + "type": "object" + }, + "Request.ClusterRestart": { + "description": "Restart cluster", + "type": "object" + }, + "Request.ClusterStart": { + "description": "Start cluster", + "type": "object" + }, + "Request.ClusterStop": { + "description": "Stop cluster", + "type": "object" + }, + "Request.CreateSetting": { + "description": "Create new setting", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "object" + } + } + }, + "Request.Environment": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "environment for production" + }, + "name": { + "type": "string", + "example": "production" + } + } + }, + "Request.ProjectCreate": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Default project" + }, + "name": { + "type": "string", + "example": "default" + } + } + }, + "Request.ProjectPatch": { + "type": "object", + "properties": { + "description": { + "type": "string", + "x-nullable": true + }, + "name": { + "type": "string", + "x-nullable": true + } + } + }, + "Request.SecretCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "aws key" + }, + "project_id": { + "type": "integer", + "example": 1 + }, + "type": { + "$ref": "#/definitions/Secret.Type" + }, + "value": { + "type": "object", + "$ref": "#/definitions/Request.SecretValue" + } + } + }, + "Request.SecretPatch": { + "type": "object", + "properties": { + "name": { + "type": "string", + "x-nullable": true, + "example": "aws key" + }, + "type": { + "type": "string", + "x-nullable": true, + "example": "aws" + }, + "value": { + "description": "Secret value in base64", + "type": "string", + "x-nullable": true, + "example": "c2VjcmV0" + } + } + }, + "Request.SecretValue": { + "type": "object", + "properties": { + "aws": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Aws" + }, + "azure": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Azure" + }, + "digitalocean": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.DigitalOcean" + }, + "gcp": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Gcp" + }, + "hetzner": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Hetzner" + }, + "password": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Password" + }, + "ssh_key": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.SshKey" + } + } + }, + "Request.SecretValue.Aws": { + "type": "object", + "properties": { + "AWS_ACCESS_KEY_ID": { + "type": "string" + }, + "AWS_SECRET_ACCESS_KEY": { + "type": "string" + } + } + }, + "Request.SecretValue.Azure": { + "type": "object", + "properties": { + "AZURE_CLIENT_ID": { + "type": "string" + }, + "AZURE_SECRET": { + "type": "string" + }, + "AZURE_SUBSCRIPTION_ID": { + "type": "string" + }, + "AZURE_TENANT": { + "type": "string" + } + } + }, + "Request.SecretValue.DigitalOcean": { + "type": "object", + "properties": { + "DO_API_TOKEN": { + "type": "string" + } + } + }, + "Request.SecretValue.Gcp": { + "type": "object", + "properties": { + "GCP_SERVICE_ACCOUNT_CONTENTS": { + "type": "string" + } + } + }, + "Request.SecretValue.Hetzner": { + "type": "object", + "properties": { + "HCLOUD_API_TOKEN": { + "type": "string" + } + } + }, + "Request.SecretValue.Password": { + "type": "object", + "properties": { + "PASSWORD": { + "type": "string" + }, + "USERNAME": { + "type": "string" + } + } + }, + "Request.SecretValue.SshKey": { + "type": "object", + "properties": { + "SSH_PRIVATE_KEY": { + "type": "string" + } + } + }, + "Response.ClusterCreate": { + "description": "Response struct for cluster creation", + "type": "object", + "properties": { + "cluster_id": { + "description": "unique code for cluster", + "type": "integer" + }, + "operation_id": { + "description": "operation id", + "type": "integer" + } + } + }, + "Response.ClusterDefaultName": { + "description": "Response struct for cluster default name", + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "postgres-cluster-01" + } + } + }, + "Response.ClusterLogs": { + "description": "Logs for cluster", + "type": "object", + "properties": { + "logs": { + "description": "all available logs", + "type": "string" + } + } + }, + "Response.ClustersInfo": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/ClusterInfo" + } + }, + "meta": { + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.DatabaseExtension": { + "description": "Info about database extension", + "type": "object", + "properties": { + "contrib": { + "type": "boolean", + "example": false + }, + "description": { + "type": "string", + "x-nullable": true, + "example": "Citus is PostgreSQL extension that transforms..." + }, + "image": { + "type": "string", + "x-nullable": true, + "example": "citus.png" + }, + "name": { + "type": "string", + "example": "Citus" + }, + "postgres_max_version": { + "type": "string", + "x-nullable": true, + "example": "16" + }, + "postgres_min_version": { + "type": "string", + "x-nullable": true, + "example": "11" + }, + "url": { + "type": "string", + "x-nullable": true, + "example": "https://github.com/citusdata/citus" + } + } + }, + "Response.DatabaseExtensions": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.DatabaseExtension" + } + }, + "meta": { + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.DeploymentInfo": { + "description": "Deployment info", + "type": "object", + "properties": { + "avatar_url": { + "type": "string" + }, + "cloud_regions": { + "description": "List of available regions for current deployment", + "type": "array", + "items": { + "$ref": "#/definitions/DeploymentInfo.CloudRegion" + } + }, + "code": { + "type": "string", + "example": "aws" + }, + "description": { + "type": "string", + "example": "Amazon web services" + }, + "instance_types": { + "description": "Lists of available instance types", + "type": "object", + "properties": { + "large": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + } + }, + "medium": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + } + }, + "small": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + }, + "x-nullable": true + } + } + }, + "volumes": { + "description": "Hardware disks info", + "type": "array", + "items": { + "type": "object", + "properties": { + "currency": { + "description": "Price currency", + "type": "string", + "example": "$" + }, + "is_default": { + "description": "Default volume", + "type": "boolean", + "x-nullable": true, + "example": false + }, + "max_size": { + "description": "Sets in GB", + "type": "integer", + "example": 256 + }, + "min_size": { + "description": "Sets in GB", + "type": "integer", + "example": 10 + }, + "price_monthly": { + "description": "Price for disk by months", + "type": "number", + "example": 0.1 + }, + "volume_description": { + "description": "Volume description", + "type": "string", + "example": "General purpose SSD disk" + }, + "volume_type": { + "description": "Volume type", + "type": "string", + "example": "gp3" + } + } + } + } + } + }, + "Response.DeploymentsInfo": { + "type": "object", + "title": "Deployments info", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.DeploymentInfo" + } + }, + "meta": { + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Environment": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "description": { + "type": "string", + "x-nullable": true, + "example": "environment for production" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "production" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + } + } + }, + "Response.EnvironmentsList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Environment" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Error": { + "type": "object", + "title": "Error object", + "properties": { + "code": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "Response.Operation": { + "type": "object", + "properties": { + "cluster_name": { + "type": "string", + "example": "drm-prod-cluster" + }, + "environment": { + "type": "string", + "example": "production" + }, + "finished": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "started": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "status": { + "type": "string", + "example": "success" + }, + "type": { + "type": "string", + "example": "deploy" + } + } + }, + "Response.OperationsList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Operation" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.PostgresVersion": { + "type": "object", + "properties": { + "end_of_life": { + "type": "string", + "format": "date", + "example": "2022-11-10" + }, + "major_version": { + "type": "integer", + "example": 10 + }, + "release_date": { + "type": "string", + "format": "date", + "example": "2017-10-05" + } + } + }, + "Response.PostgresVersions": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.PostgresVersion" + } + } + } + }, + "Response.Project": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "description": { + "type": "string", + "x-nullable": true + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + } + } + }, + "Response.ProjectsList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Project" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.SecretInfo": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_used": { + "type": "boolean", + "example": "true" + }, + "name": { + "type": "string", + "example": "aws key" + }, + "project_id": { + "type": "integer", + "example": 1 + }, + "type": { + "$ref": "#/definitions/Secret.Type" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + }, + "used_by_clusters": { + "type": "string", + "x-nullable": true, + "example": "mds-prod, drm-prod" + } + } + }, + "Response.SecretInfoList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.SecretInfo" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Setting": { + "description": "Setting", + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "datetime" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "datetime", + "x-nullable": true + }, + "value": { + "type": "object" + } + } + }, + "Response.Settings": { + "description": "List of settings", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Setting" + } + }, + "mete": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Version": { + "type": "object", + "title": "Version response", + "properties": { + "version": { + "type": "string", + "example": "v1.0.0" + } + } + }, + "Secret.Type": { + "type": "string", + "enum": [ + "aws", + "gcp", + "hetzner", + "ssh_key", + "digitalocean", + "password", + "azure" + ] + } + } +}`)) + FlatSwaggerJSON = json.RawMessage([]byte(`{ + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "swagger": "2.0", + "info": { + "description": "API for PG Console WEB", + "title": "PG Console", + "version": "1.0.0" + }, + "host": "localhost:8080", + "basePath": "/api/v1", + "paths": { + "/clusters": { + "get": { + "tags": [ + "cluster" + ], + "summary": "Get info about clusters", + "parameters": [ + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "project_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Filter by name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Filter by location", + "name": "location", + "in": "query" + }, + { + "type": "string", + "description": "Filter by environment", + "name": "environment", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by server_count", + "name": "server_count", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by postgres_version", + "name": "postgres_version", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Created at after this date", + "name": "created_at_from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Created at till this date", + "name": "created_at_to", + "in": "query" + }, + { + "type": "string", + "description": "Sort by fields. Example: sort_by=id,-name,created_at,updated_at\n Supported values:\n - id\n - name\n - created_at\n - updated_at\n - environment\n - project\n - status\n - location\n - server_count\n - postgres_version\n", + "name": "sort_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClustersInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "cluster" + ], + "summary": "Create new cluster", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/default_name": { + "get": { + "tags": [ + "cluster" + ], + "summary": "Get cluster default name", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterDefaultName" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}": { + "get": { + "tags": [ + "cluster" + ], + "summary": "Get cluster info", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ClusterInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "delete": { + "tags": [ + "cluster" + ], + "summary": "Delete cluster", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/refresh": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Refresh cluster info", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ClusterInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/reinit": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Reinit cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterReinit" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/reload": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Reload cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterReload" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/remove": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Remove cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterRemove" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/restart": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Restart cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterRestart" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/start": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Start cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterStart" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/clusters/{id}/stop": { + "post": { + "tags": [ + "cluster" + ], + "summary": "Stop cluster", + "deprecated": true, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ClusterStop" + } + }, + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ClusterCreate" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/database/extensions": { + "get": { + "tags": [ + "dictionary" + ], + "summary": "Info about available database extensions", + "parameters": [ + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "enum": [ + "all", + "contrib", + "third_party" + ], + "type": "string", + "default": "all", + "name": "extension_type", + "in": "query" + }, + { + "type": "string", + "name": "postgres_version", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.DatabaseExtensions" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/environments": { + "get": { + "tags": [ + "environment" + ], + "summary": "Get environemtns list", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.EnvironmentsList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "environment" + ], + "summary": "Create environemtn", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.Environment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Environment" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/environments/{id}": { + "delete": { + "tags": [ + "environment" + ], + "summary": "Delete environment", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/external/deployments": { + "get": { + "tags": [ + "dictionary" + ], + "summary": "Get full info about available external deployments", + "parameters": [ + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.DeploymentsInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/operations": { + "get": { + "tags": [ + "operation" + ], + "summary": "Get operations list for current project", + "parameters": [ + { + "type": "integer", + "description": "Required parameter for filter", + "name": "project_id", + "in": "query", + "required": true + }, + { + "type": "string", + "format": "date-time", + "description": "Operations started after this date", + "name": "start_date", + "in": "query", + "required": true + }, + { + "type": "string", + "format": "date-time", + "description": "Operations started till this date", + "name": "end_date", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Filter by cluster_name", + "name": "cluster_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by type", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Filter by environment", + "name": "environment", + "in": "query" + }, + { + "type": "string", + "description": "Sort by fields. Example: sort_by=cluster_name,-type,status,id,created_at,updated_at\n Supported valuese:\n - id\n - cluster_name\n - type\n - status\n - started_at\n - updated_at\n - cluster\n - environment\n", + "name": "sort_by", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.OperationsList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/operations/{id}/log": { + "get": { + "consumes": [ + "plain/text" + ], + "tags": [ + "operation" + ], + "summary": "Get operation log by operation_id", + "parameters": [ + { + "type": "integer", + "description": "Operation id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + }, + "headers": { + "content-type": { + "type": "string" + }, + "x-log-completed": { + "type": "boolean" + } + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/postgres_versions": { + "get": { + "tags": [ + "dictionary" + ], + "summary": "Get supported postgres versions", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.PostgresVersions" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/projects": { + "get": { + "tags": [ + "project" + ], + "summary": "Get projects list", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.ProjectsList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "project" + ], + "summary": "Create new project", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ProjectCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Project" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/projects/{id}": { + "delete": { + "tags": [ + "project" + ], + "summary": "Delete project", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "patch": { + "tags": [ + "project" + ], + "summary": "Change project", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ProjectPatch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Project" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/secrets": { + "get": { + "tags": [ + "secret" + ], + "summary": "Get secrets list", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "project_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Filter by name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by type", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "Sort by fields. Example: sort_by=id,name,-type,created_at,updated_at", + "name": "sort_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.SecretInfoList" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "secret" + ], + "summary": "Create new secret", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.SecretCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.SecretInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/secrets/{id}": { + "delete": { + "tags": [ + "secret" + ], + "summary": "Delete secret", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK" + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "patch": { + "tags": [ + "secret" + ], + "summary": "Change secret", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.SecretPatch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.SecretInfo" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/servers/{id}": { + "delete": { + "tags": [ + "cluster" + ], + "summary": "Delete server from cluster", + "parameters": [ + { + "type": "integer", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "OK", + "headers": { + "x-cluster-id": { + "type": "integer" + } + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/settings": { + "get": { + "tags": [ + "setting" + ], + "summary": "Get settings", + "parameters": [ + { + "type": "string", + "description": "Filter by name", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Settings" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + }, + "post": { + "tags": [ + "setting" + ], + "summary": "Create new setting", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.CreateSetting" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Setting" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/settings/{name}": { + "patch": { + "tags": [ + "setting" + ], + "summary": "Changed setting", + "parameters": [ + { + "type": "string", + "name": "name", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Request.ChangeSetting" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Setting" + } + }, + "400": { + "description": "Error", + "schema": { + "$ref": "#/definitions/Response.Error" + } + } + } + } + }, + "/version": { + "get": { + "tags": [ + "system" + ], + "summary": "Get version of server", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Response.Version" + } + } + } + } + } + }, + "definitions": { + "ClusterInfo": { + "description": "Cluster info", + "type": "object", + "properties": { + "cluster_location": { + "description": "Code of location", + "type": "string", + "example": "eu-north-1" + }, + "connection_info": { + "type": "object" + }, + "creation_time": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "description": { + "type": "string" + }, + "environment": { + "type": "string", + "example": "production" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "example": "drm-prod-pgcluster" + }, + "postgres_version": { + "type": "integer", + "format": "int32", + "example": 15 + }, + "project_name": { + "description": "Project for cluster", + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/ClusterInfo.Instance" + } + }, + "status": { + "type": "string", + "example": "healthy" + } + } + }, + "ClusterInfo.AdditionalSettings": { + "description": "Additional settings for cluster", + "type": "object", + "properties": { + "connection_info": { + "type": "object" + } + } + }, + "ClusterInfo.Instance": { + "description": "Instance info for current cluster", + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "ip": { + "type": "string", + "example": "10.128.64.141" + }, + "lag": { + "type": "integer", + "format": "int64", + "x-nullable": true, + "example": 0 + }, + "name": { + "type": "string", + "example": "pgnode1" + }, + "pending_restart": { + "type": "boolean", + "x-nullable": true, + "example": false + }, + "role": { + "type": "string", + "example": "leader" + }, + "status": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "timeline": { + "type": "integer", + "format": "int64", + "x-nullable": true, + "example": 1 + } + } + }, + "Deployment.CloudImage": { + "type": "object", + "properties": { + "arch": { + "type": "string", + "example": "amd64" + }, + "image": { + "type": "object", + "example": "{\"server_image\": \"ami-078b3985bbc361448\"}" + }, + "os_name": { + "type": "string", + "example": "Ubuntu" + }, + "os_version": { + "type": "string", + "example": "22.04 LTS" + }, + "updated_at": { + "type": "string", + "format": "datetime" + } + } + }, + "Deployment.InstanceType": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "m5.2xlarge" + }, + "cpu": { + "type": "integer", + "example": 8 + }, + "currency": { + "description": "Price currency", + "type": "string", + "example": "$" + }, + "price_hourly": { + "description": "Price for 1 instance by hour", + "type": "number", + "example": 0.01 + }, + "price_monthly": { + "description": "Price for 1 instance by month", + "type": "number", + "example": 1.2 + }, + "ram": { + "type": "integer", + "example": 256 + } + } + }, + "DeploymentInfo.CloudRegion": { + "type": "object", + "properties": { + "code": { + "description": "unique parameter for DB", + "type": "string", + "example": "north_america" + }, + "datacenters": { + "description": "List of datacenters for this region", + "type": "array", + "items": { + "$ref": "#/definitions/DeploymentInfoCloudRegionDatacentersItems0" + } + }, + "name": { + "description": "Field for web", + "type": "string", + "example": "North America" + } + } + }, + "DeploymentInfoCloudRegionDatacentersItems0": { + "type": "object", + "properties": { + "cloud_image": { + "$ref": "#/definitions/Deployment.CloudImage" + }, + "code": { + "type": "string", + "example": "ca-central-1" + }, + "location": { + "type": "string", + "example": "Canada (central)" + } + } + }, + "Meta.Pagination": { + "type": "object", + "title": "Pagination info for list requests", + "properties": { + "count": { + "type": "integer", + "x-nullable": true + }, + "limit": { + "type": "integer", + "x-nullable": true + }, + "offset": { + "type": "integer", + "x-nullable": true + } + } + }, + "Request.ChangeSetting": { + "description": "Change setting", + "type": "object", + "properties": { + "value": { + "type": "object", + "x-nullable": true + } + } + }, + "Request.ClusterCreate": { + "description": "Request struct for cluster creation", + "type": "object", + "properties": { + "auth_info": { + "description": "Info for deployment system authorization", + "type": "object", + "properties": { + "secret_id": { + "type": "integer", + "example": 1 + } + } + }, + "description": { + "description": "Info about cluster", + "type": "string" + }, + "environment_id": { + "description": "Project environment", + "type": "integer" + }, + "envs": { + "type": "array", + "items": { + "type": "string" + } + }, + "extra_vars": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "example": "drm-prod-pgcluster" + }, + "project_id": { + "description": "Project for new cluster", + "type": "integer" + } + } + }, + "Request.ClusterReinit": { + "description": "Reinit cluster", + "type": "object" + }, + "Request.ClusterReload": { + "description": "Reload cluster", + "type": "object" + }, + "Request.ClusterRemove": { + "description": "Remove cluster", + "type": "object" + }, + "Request.ClusterRestart": { + "description": "Restart cluster", + "type": "object" + }, + "Request.ClusterStart": { + "description": "Start cluster", + "type": "object" + }, + "Request.ClusterStop": { + "description": "Stop cluster", + "type": "object" + }, + "Request.CreateSetting": { + "description": "Create new setting", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "object" + } + } + }, + "Request.Environment": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "environment for production" + }, + "name": { + "type": "string", + "example": "production" + } + } + }, + "Request.ProjectCreate": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Default project" + }, + "name": { + "type": "string", + "example": "default" + } + } + }, + "Request.ProjectPatch": { + "type": "object", + "properties": { + "description": { + "type": "string", + "x-nullable": true + }, + "name": { + "type": "string", + "x-nullable": true + } + } + }, + "Request.SecretCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "aws key" + }, + "project_id": { + "type": "integer", + "example": 1 + }, + "type": { + "$ref": "#/definitions/Secret.Type" + }, + "value": { + "type": "object", + "$ref": "#/definitions/Request.SecretValue" + } + } + }, + "Request.SecretPatch": { + "type": "object", + "properties": { + "name": { + "type": "string", + "x-nullable": true, + "example": "aws key" + }, + "type": { + "type": "string", + "x-nullable": true, + "example": "aws" + }, + "value": { + "description": "Secret value in base64", + "type": "string", + "x-nullable": true, + "example": "c2VjcmV0" + } + } + }, + "Request.SecretValue": { + "type": "object", + "properties": { + "aws": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Aws" + }, + "azure": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Azure" + }, + "digitalocean": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.DigitalOcean" + }, + "gcp": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Gcp" + }, + "hetzner": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Hetzner" + }, + "password": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.Password" + }, + "ssh_key": { + "type": "object", + "x-nullable": true, + "$ref": "#/definitions/Request.SecretValue.SshKey" + } + } + }, + "Request.SecretValue.Aws": { + "type": "object", + "properties": { + "AWS_ACCESS_KEY_ID": { + "type": "string" + }, + "AWS_SECRET_ACCESS_KEY": { + "type": "string" + } + } + }, + "Request.SecretValue.Azure": { + "type": "object", + "properties": { + "AZURE_CLIENT_ID": { + "type": "string" + }, + "AZURE_SECRET": { + "type": "string" + }, + "AZURE_SUBSCRIPTION_ID": { + "type": "string" + }, + "AZURE_TENANT": { + "type": "string" + } + } + }, + "Request.SecretValue.DigitalOcean": { + "type": "object", + "properties": { + "DO_API_TOKEN": { + "type": "string" + } + } + }, + "Request.SecretValue.Gcp": { + "type": "object", + "properties": { + "GCP_SERVICE_ACCOUNT_CONTENTS": { + "type": "string" + } + } + }, + "Request.SecretValue.Hetzner": { + "type": "object", + "properties": { + "HCLOUD_API_TOKEN": { + "type": "string" + } + } + }, + "Request.SecretValue.Password": { + "type": "object", + "properties": { + "PASSWORD": { + "type": "string" + }, + "USERNAME": { + "type": "string" + } + } + }, + "Request.SecretValue.SshKey": { + "type": "object", + "properties": { + "SSH_PRIVATE_KEY": { + "type": "string" + } + } + }, + "RequestClusterCreateAuthInfo": { + "description": "Info for deployment system authorization", + "type": "object", + "properties": { + "secret_id": { + "type": "integer", + "example": 1 + } + } + }, + "Response.ClusterCreate": { + "description": "Response struct for cluster creation", + "type": "object", + "properties": { + "cluster_id": { + "description": "unique code for cluster", + "type": "integer" + }, + "operation_id": { + "description": "operation id", + "type": "integer" + } + } + }, + "Response.ClusterDefaultName": { + "description": "Response struct for cluster default name", + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "postgres-cluster-01" + } + } + }, + "Response.ClusterLogs": { + "description": "Logs for cluster", + "type": "object", + "properties": { + "logs": { + "description": "all available logs", + "type": "string" + } + } + }, + "Response.ClustersInfo": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/ClusterInfo" + } + }, + "meta": { + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.DatabaseExtension": { + "description": "Info about database extension", + "type": "object", + "properties": { + "contrib": { + "type": "boolean", + "example": false + }, + "description": { + "type": "string", + "x-nullable": true, + "example": "Citus is PostgreSQL extension that transforms..." + }, + "image": { + "type": "string", + "x-nullable": true, + "example": "citus.png" + }, + "name": { + "type": "string", + "example": "Citus" + }, + "postgres_max_version": { + "type": "string", + "x-nullable": true, + "example": "16" + }, + "postgres_min_version": { + "type": "string", + "x-nullable": true, + "example": "11" + }, + "url": { + "type": "string", + "x-nullable": true, + "example": "https://github.com/citusdata/citus" + } + } + }, + "Response.DatabaseExtensions": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.DatabaseExtension" + } + }, + "meta": { + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.DeploymentInfo": { + "description": "Deployment info", + "type": "object", + "properties": { + "avatar_url": { + "type": "string" + }, + "cloud_regions": { + "description": "List of available regions for current deployment", + "type": "array", + "items": { + "$ref": "#/definitions/DeploymentInfo.CloudRegion" + } + }, + "code": { + "type": "string", + "example": "aws" + }, + "description": { + "type": "string", + "example": "Amazon web services" + }, + "instance_types": { + "description": "Lists of available instance types", + "type": "object", + "properties": { + "large": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + } + }, + "medium": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + } + }, + "small": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + }, + "x-nullable": true + } + } + }, + "volumes": { + "description": "Hardware disks info", + "type": "array", + "items": { + "$ref": "#/definitions/ResponseDeploymentInfoVolumesItems0" + } + } + } + }, + "Response.DeploymentsInfo": { + "type": "object", + "title": "Deployments info", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.DeploymentInfo" + } + }, + "meta": { + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Environment": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "description": { + "type": "string", + "x-nullable": true, + "example": "environment for production" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "production" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + } + } + }, + "Response.EnvironmentsList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Environment" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Error": { + "type": "object", + "title": "Error object", + "properties": { + "code": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "Response.Operation": { + "type": "object", + "properties": { + "cluster_name": { + "type": "string", + "example": "drm-prod-cluster" + }, + "environment": { + "type": "string", + "example": "production" + }, + "finished": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "started": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "status": { + "type": "string", + "example": "success" + }, + "type": { + "type": "string", + "example": "deploy" + } + } + }, + "Response.OperationsList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Operation" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.PostgresVersion": { + "type": "object", + "properties": { + "end_of_life": { + "type": "string", + "format": "date", + "example": "2022-11-10" + }, + "major_version": { + "type": "integer", + "example": 10 + }, + "release_date": { + "type": "string", + "format": "date", + "example": "2017-10-05" + } + } + }, + "Response.PostgresVersions": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.PostgresVersion" + } + } + } + }, + "Response.Project": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "description": { + "type": "string", + "x-nullable": true + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + } + } + }, + "Response.ProjectsList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Project" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.SecretInfo": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time", + "example": "16.10.2023T11:20:00Z" + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_used": { + "type": "boolean", + "example": "true" + }, + "name": { + "type": "string", + "example": "aws key" + }, + "project_id": { + "type": "integer", + "example": 1 + }, + "type": { + "$ref": "#/definitions/Secret.Type" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-nullable": true, + "example": "16.10.2023T11:20:00Z" + }, + "used_by_clusters": { + "type": "string", + "x-nullable": true, + "example": "mds-prod, drm-prod" + } + } + }, + "Response.SecretInfoList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.SecretInfo" + } + }, + "meta": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Setting": { + "description": "Setting", + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "datetime" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "datetime", + "x-nullable": true + }, + "value": { + "type": "object" + } + } + }, + "Response.Settings": { + "description": "List of settings", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/Response.Setting" + } + }, + "mete": { + "type": "object", + "$ref": "#/definitions/Meta.Pagination" + } + } + }, + "Response.Version": { + "type": "object", + "title": "Version response", + "properties": { + "version": { + "type": "string", + "example": "v1.0.0" + } + } + }, + "ResponseDeploymentInfoInstanceTypes": { + "description": "Lists of available instance types", + "type": "object", + "properties": { + "large": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + } + }, + "medium": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + } + }, + "small": { + "type": "array", + "items": { + "$ref": "#/definitions/Deployment.InstanceType" + }, + "x-nullable": true + } + } + }, + "ResponseDeploymentInfoVolumesItems0": { + "type": "object", + "properties": { + "currency": { + "description": "Price currency", + "type": "string", + "example": "$" + }, + "is_default": { + "description": "Default volume", + "type": "boolean", + "x-nullable": true, + "example": false + }, + "max_size": { + "description": "Sets in GB", + "type": "integer", + "example": 256 + }, + "min_size": { + "description": "Sets in GB", + "type": "integer", + "example": 10 + }, + "price_monthly": { + "description": "Price for disk by months", + "type": "number", + "example": 0.1 + }, + "volume_description": { + "description": "Volume description", + "type": "string", + "example": "General purpose SSD disk" + }, + "volume_type": { + "description": "Volume type", + "type": "string", + "example": "gp3" + } + } + }, + "Secret.Type": { + "type": "string", + "enum": [ + "aws", + "gcp", + "hetzner", + "ssh_key", + "digitalocean", + "password", + "azure" + ] + } + } +}`)) +} diff --git a/console/service/restapi/operations/cluster/delete_clusters_id.go b/console/service/restapi/operations/cluster/delete_clusters_id.go new file mode 100644 index 000000000..9894c8295 --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_clusters_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// DeleteClustersIDHandlerFunc turns a function with the right signature into a delete clusters ID handler +type DeleteClustersIDHandlerFunc func(DeleteClustersIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteClustersIDHandlerFunc) Handle(params DeleteClustersIDParams) middleware.Responder { + return fn(params) +} + +// DeleteClustersIDHandler interface for that can handle valid delete clusters ID params +type DeleteClustersIDHandler interface { + Handle(DeleteClustersIDParams) middleware.Responder +} + +// NewDeleteClustersID creates a new http.Handler for the delete clusters ID operation +func NewDeleteClustersID(ctx *middleware.Context, handler DeleteClustersIDHandler) *DeleteClustersID { + return &DeleteClustersID{Context: ctx, Handler: handler} +} + +/* + DeleteClustersID swagger:route DELETE /clusters/{id} cluster deleteClustersId + +Delete cluster +*/ +type DeleteClustersID struct { + Context *middleware.Context + Handler DeleteClustersIDHandler +} + +func (o *DeleteClustersID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewDeleteClustersIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/delete_clusters_id_parameters.go b/console/service/restapi/operations/cluster/delete_clusters_id_parameters.go new file mode 100644 index 000000000..a0a865273 --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_clusters_id_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewDeleteClustersIDParams creates a new DeleteClustersIDParams object +// +// There are no default values defined in the spec. +func NewDeleteClustersIDParams() DeleteClustersIDParams { + + return DeleteClustersIDParams{} +} + +// DeleteClustersIDParams contains all the bound params for the delete clusters ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters DeleteClustersID +type DeleteClustersIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewDeleteClustersIDParams() beforehand. +func (o *DeleteClustersIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *DeleteClustersIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/delete_clusters_id_responses.go b/console/service/restapi/operations/cluster/delete_clusters_id_responses.go new file mode 100644 index 000000000..046bd3dd3 --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_clusters_id_responses.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// DeleteClustersIDNoContentCode is the HTTP code returned for type DeleteClustersIDNoContent +const DeleteClustersIDNoContentCode int = 204 + +/* +DeleteClustersIDNoContent OK + +swagger:response deleteClustersIdNoContent +*/ +type DeleteClustersIDNoContent struct { +} + +// NewDeleteClustersIDNoContent creates DeleteClustersIDNoContent with default headers values +func NewDeleteClustersIDNoContent() *DeleteClustersIDNoContent { + + return &DeleteClustersIDNoContent{} +} + +// WriteResponse to the client +func (o *DeleteClustersIDNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +// DeleteClustersIDBadRequestCode is the HTTP code returned for type DeleteClustersIDBadRequest +const DeleteClustersIDBadRequestCode int = 400 + +/* +DeleteClustersIDBadRequest Error + +swagger:response deleteClustersIdBadRequest +*/ +type DeleteClustersIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewDeleteClustersIDBadRequest creates DeleteClustersIDBadRequest with default headers values +func NewDeleteClustersIDBadRequest() *DeleteClustersIDBadRequest { + + return &DeleteClustersIDBadRequest{} +} + +// WithPayload adds the payload to the delete clusters Id bad request response +func (o *DeleteClustersIDBadRequest) WithPayload(payload *models.ResponseError) *DeleteClustersIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the delete clusters Id bad request response +func (o *DeleteClustersIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *DeleteClustersIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/delete_clusters_id_urlbuilder.go b/console/service/restapi/operations/cluster/delete_clusters_id_urlbuilder.go new file mode 100644 index 000000000..7eebf0b6d --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_clusters_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// DeleteClustersIDURL generates an URL for the delete clusters ID operation +type DeleteClustersIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteClustersIDURL) WithBasePath(bp string) *DeleteClustersIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteClustersIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteClustersIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on DeleteClustersIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *DeleteClustersIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *DeleteClustersIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteClustersIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteClustersIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteClustersIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *DeleteClustersIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/delete_servers_id.go b/console/service/restapi/operations/cluster/delete_servers_id.go new file mode 100644 index 000000000..cd4f5b96d --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_servers_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// DeleteServersIDHandlerFunc turns a function with the right signature into a delete servers ID handler +type DeleteServersIDHandlerFunc func(DeleteServersIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteServersIDHandlerFunc) Handle(params DeleteServersIDParams) middleware.Responder { + return fn(params) +} + +// DeleteServersIDHandler interface for that can handle valid delete servers ID params +type DeleteServersIDHandler interface { + Handle(DeleteServersIDParams) middleware.Responder +} + +// NewDeleteServersID creates a new http.Handler for the delete servers ID operation +func NewDeleteServersID(ctx *middleware.Context, handler DeleteServersIDHandler) *DeleteServersID { + return &DeleteServersID{Context: ctx, Handler: handler} +} + +/* + DeleteServersID swagger:route DELETE /servers/{id} cluster deleteServersId + +Delete server from cluster +*/ +type DeleteServersID struct { + Context *middleware.Context + Handler DeleteServersIDHandler +} + +func (o *DeleteServersID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewDeleteServersIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/delete_servers_id_parameters.go b/console/service/restapi/operations/cluster/delete_servers_id_parameters.go new file mode 100644 index 000000000..66c4b8ffc --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_servers_id_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewDeleteServersIDParams creates a new DeleteServersIDParams object +// +// There are no default values defined in the spec. +func NewDeleteServersIDParams() DeleteServersIDParams { + + return DeleteServersIDParams{} +} + +// DeleteServersIDParams contains all the bound params for the delete servers ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters DeleteServersID +type DeleteServersIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewDeleteServersIDParams() beforehand. +func (o *DeleteServersIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *DeleteServersIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/delete_servers_id_responses.go b/console/service/restapi/operations/cluster/delete_servers_id_responses.go new file mode 100644 index 000000000..97c1a40d5 --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_servers_id_responses.go @@ -0,0 +1,107 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// DeleteServersIDNoContentCode is the HTTP code returned for type DeleteServersIDNoContent +const DeleteServersIDNoContentCode int = 204 + +/* +DeleteServersIDNoContent OK + +swagger:response deleteServersIdNoContent +*/ +type DeleteServersIDNoContent struct { + /* + + */ + XClusterID int64 `json:"x-cluster-id"` +} + +// NewDeleteServersIDNoContent creates DeleteServersIDNoContent with default headers values +func NewDeleteServersIDNoContent() *DeleteServersIDNoContent { + + return &DeleteServersIDNoContent{} +} + +// WithXClusterID adds the xClusterId to the delete servers Id no content response +func (o *DeleteServersIDNoContent) WithXClusterID(xClusterID int64) *DeleteServersIDNoContent { + o.XClusterID = xClusterID + return o +} + +// SetXClusterID sets the xClusterId to the delete servers Id no content response +func (o *DeleteServersIDNoContent) SetXClusterID(xClusterID int64) { + o.XClusterID = xClusterID +} + +// WriteResponse to the client +func (o *DeleteServersIDNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // response header x-cluster-id + + xClusterID := swag.FormatInt64(o.XClusterID) + if xClusterID != "" { + rw.Header().Set("x-cluster-id", xClusterID) + } + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +// DeleteServersIDBadRequestCode is the HTTP code returned for type DeleteServersIDBadRequest +const DeleteServersIDBadRequestCode int = 400 + +/* +DeleteServersIDBadRequest Error + +swagger:response deleteServersIdBadRequest +*/ +type DeleteServersIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewDeleteServersIDBadRequest creates DeleteServersIDBadRequest with default headers values +func NewDeleteServersIDBadRequest() *DeleteServersIDBadRequest { + + return &DeleteServersIDBadRequest{} +} + +// WithPayload adds the payload to the delete servers Id bad request response +func (o *DeleteServersIDBadRequest) WithPayload(payload *models.ResponseError) *DeleteServersIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the delete servers Id bad request response +func (o *DeleteServersIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *DeleteServersIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/delete_servers_id_urlbuilder.go b/console/service/restapi/operations/cluster/delete_servers_id_urlbuilder.go new file mode 100644 index 000000000..cda5d3b94 --- /dev/null +++ b/console/service/restapi/operations/cluster/delete_servers_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// DeleteServersIDURL generates an URL for the delete servers ID operation +type DeleteServersIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteServersIDURL) WithBasePath(bp string) *DeleteServersIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteServersIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteServersIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/servers/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on DeleteServersIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *DeleteServersIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *DeleteServersIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteServersIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteServersIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteServersIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *DeleteServersIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/get_clusters.go b/console/service/restapi/operations/cluster/get_clusters.go new file mode 100644 index 000000000..a52820fe0 --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetClustersHandlerFunc turns a function with the right signature into a get clusters handler +type GetClustersHandlerFunc func(GetClustersParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetClustersHandlerFunc) Handle(params GetClustersParams) middleware.Responder { + return fn(params) +} + +// GetClustersHandler interface for that can handle valid get clusters params +type GetClustersHandler interface { + Handle(GetClustersParams) middleware.Responder +} + +// NewGetClusters creates a new http.Handler for the get clusters operation +func NewGetClusters(ctx *middleware.Context, handler GetClustersHandler) *GetClusters { + return &GetClusters{Context: ctx, Handler: handler} +} + +/* + GetClusters swagger:route GET /clusters cluster getClusters + +Get info about clusters +*/ +type GetClusters struct { + Context *middleware.Context + Handler GetClustersHandler +} + +func (o *GetClusters) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetClustersParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/get_clusters_default_name.go b/console/service/restapi/operations/cluster/get_clusters_default_name.go new file mode 100644 index 000000000..114a624d3 --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_default_name.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetClustersDefaultNameHandlerFunc turns a function with the right signature into a get clusters default name handler +type GetClustersDefaultNameHandlerFunc func(GetClustersDefaultNameParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetClustersDefaultNameHandlerFunc) Handle(params GetClustersDefaultNameParams) middleware.Responder { + return fn(params) +} + +// GetClustersDefaultNameHandler interface for that can handle valid get clusters default name params +type GetClustersDefaultNameHandler interface { + Handle(GetClustersDefaultNameParams) middleware.Responder +} + +// NewGetClustersDefaultName creates a new http.Handler for the get clusters default name operation +func NewGetClustersDefaultName(ctx *middleware.Context, handler GetClustersDefaultNameHandler) *GetClustersDefaultName { + return &GetClustersDefaultName{Context: ctx, Handler: handler} +} + +/* + GetClustersDefaultName swagger:route GET /clusters/default_name cluster getClustersDefaultName + +Get cluster default name +*/ +type GetClustersDefaultName struct { + Context *middleware.Context + Handler GetClustersDefaultNameHandler +} + +func (o *GetClustersDefaultName) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetClustersDefaultNameParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/get_clusters_default_name_parameters.go b/console/service/restapi/operations/cluster/get_clusters_default_name_parameters.go new file mode 100644 index 000000000..29eb36284 --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_default_name_parameters.go @@ -0,0 +1,46 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" +) + +// NewGetClustersDefaultNameParams creates a new GetClustersDefaultNameParams object +// +// There are no default values defined in the spec. +func NewGetClustersDefaultNameParams() GetClustersDefaultNameParams { + + return GetClustersDefaultNameParams{} +} + +// GetClustersDefaultNameParams contains all the bound params for the get clusters default name operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetClustersDefaultName +type GetClustersDefaultNameParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetClustersDefaultNameParams() beforehand. +func (o *GetClustersDefaultNameParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/cluster/get_clusters_default_name_responses.go b/console/service/restapi/operations/cluster/get_clusters_default_name_responses.go new file mode 100644 index 000000000..7a2dd3e8c --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_default_name_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetClustersDefaultNameOKCode is the HTTP code returned for type GetClustersDefaultNameOK +const GetClustersDefaultNameOKCode int = 200 + +/* +GetClustersDefaultNameOK OK + +swagger:response getClustersDefaultNameOK +*/ +type GetClustersDefaultNameOK struct { + + /* + In: Body + */ + Payload *models.ResponseClusterDefaultName `json:"body,omitempty"` +} + +// NewGetClustersDefaultNameOK creates GetClustersDefaultNameOK with default headers values +func NewGetClustersDefaultNameOK() *GetClustersDefaultNameOK { + + return &GetClustersDefaultNameOK{} +} + +// WithPayload adds the payload to the get clusters default name o k response +func (o *GetClustersDefaultNameOK) WithPayload(payload *models.ResponseClusterDefaultName) *GetClustersDefaultNameOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get clusters default name o k response +func (o *GetClustersDefaultNameOK) SetPayload(payload *models.ResponseClusterDefaultName) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetClustersDefaultNameOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetClustersDefaultNameBadRequestCode is the HTTP code returned for type GetClustersDefaultNameBadRequest +const GetClustersDefaultNameBadRequestCode int = 400 + +/* +GetClustersDefaultNameBadRequest Error + +swagger:response getClustersDefaultNameBadRequest +*/ +type GetClustersDefaultNameBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetClustersDefaultNameBadRequest creates GetClustersDefaultNameBadRequest with default headers values +func NewGetClustersDefaultNameBadRequest() *GetClustersDefaultNameBadRequest { + + return &GetClustersDefaultNameBadRequest{} +} + +// WithPayload adds the payload to the get clusters default name bad request response +func (o *GetClustersDefaultNameBadRequest) WithPayload(payload *models.ResponseError) *GetClustersDefaultNameBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get clusters default name bad request response +func (o *GetClustersDefaultNameBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetClustersDefaultNameBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/get_clusters_default_name_urlbuilder.go b/console/service/restapi/operations/cluster/get_clusters_default_name_urlbuilder.go new file mode 100644 index 000000000..1ee242a2f --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_default_name_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// GetClustersDefaultNameURL generates an URL for the get clusters default name operation +type GetClustersDefaultNameURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetClustersDefaultNameURL) WithBasePath(bp string) *GetClustersDefaultNameURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetClustersDefaultNameURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetClustersDefaultNameURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/default_name" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetClustersDefaultNameURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetClustersDefaultNameURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetClustersDefaultNameURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetClustersDefaultNameURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetClustersDefaultNameURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetClustersDefaultNameURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/get_clusters_id.go b/console/service/restapi/operations/cluster/get_clusters_id.go new file mode 100644 index 000000000..e83806deb --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetClustersIDHandlerFunc turns a function with the right signature into a get clusters ID handler +type GetClustersIDHandlerFunc func(GetClustersIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetClustersIDHandlerFunc) Handle(params GetClustersIDParams) middleware.Responder { + return fn(params) +} + +// GetClustersIDHandler interface for that can handle valid get clusters ID params +type GetClustersIDHandler interface { + Handle(GetClustersIDParams) middleware.Responder +} + +// NewGetClustersID creates a new http.Handler for the get clusters ID operation +func NewGetClustersID(ctx *middleware.Context, handler GetClustersIDHandler) *GetClustersID { + return &GetClustersID{Context: ctx, Handler: handler} +} + +/* + GetClustersID swagger:route GET /clusters/{id} cluster getClustersId + +Get cluster info +*/ +type GetClustersID struct { + Context *middleware.Context + Handler GetClustersIDHandler +} + +func (o *GetClustersID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetClustersIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/get_clusters_id_parameters.go b/console/service/restapi/operations/cluster/get_clusters_id_parameters.go new file mode 100644 index 000000000..d2b206d85 --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_id_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetClustersIDParams creates a new GetClustersIDParams object +// +// There are no default values defined in the spec. +func NewGetClustersIDParams() GetClustersIDParams { + + return GetClustersIDParams{} +} + +// GetClustersIDParams contains all the bound params for the get clusters ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetClustersID +type GetClustersIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetClustersIDParams() beforehand. +func (o *GetClustersIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *GetClustersIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/get_clusters_id_responses.go b/console/service/restapi/operations/cluster/get_clusters_id_responses.go new file mode 100644 index 000000000..518085b8e --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_id_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetClustersIDOKCode is the HTTP code returned for type GetClustersIDOK +const GetClustersIDOKCode int = 200 + +/* +GetClustersIDOK OK + +swagger:response getClustersIdOK +*/ +type GetClustersIDOK struct { + + /* + In: Body + */ + Payload *models.ClusterInfo `json:"body,omitempty"` +} + +// NewGetClustersIDOK creates GetClustersIDOK with default headers values +func NewGetClustersIDOK() *GetClustersIDOK { + + return &GetClustersIDOK{} +} + +// WithPayload adds the payload to the get clusters Id o k response +func (o *GetClustersIDOK) WithPayload(payload *models.ClusterInfo) *GetClustersIDOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get clusters Id o k response +func (o *GetClustersIDOK) SetPayload(payload *models.ClusterInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetClustersIDOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetClustersIDBadRequestCode is the HTTP code returned for type GetClustersIDBadRequest +const GetClustersIDBadRequestCode int = 400 + +/* +GetClustersIDBadRequest Error + +swagger:response getClustersIdBadRequest +*/ +type GetClustersIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetClustersIDBadRequest creates GetClustersIDBadRequest with default headers values +func NewGetClustersIDBadRequest() *GetClustersIDBadRequest { + + return &GetClustersIDBadRequest{} +} + +// WithPayload adds the payload to the get clusters Id bad request response +func (o *GetClustersIDBadRequest) WithPayload(payload *models.ResponseError) *GetClustersIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get clusters Id bad request response +func (o *GetClustersIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetClustersIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/get_clusters_id_urlbuilder.go b/console/service/restapi/operations/cluster/get_clusters_id_urlbuilder.go new file mode 100644 index 000000000..ae9b0c38d --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// GetClustersIDURL generates an URL for the get clusters ID operation +type GetClustersIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetClustersIDURL) WithBasePath(bp string) *GetClustersIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetClustersIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetClustersIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on GetClustersIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetClustersIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetClustersIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetClustersIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetClustersIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetClustersIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetClustersIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/get_clusters_parameters.go b/console/service/restapi/operations/cluster/get_clusters_parameters.go new file mode 100644 index 000000000..b53e335ef --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_parameters.go @@ -0,0 +1,455 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewGetClustersParams creates a new GetClustersParams object +// +// There are no default values defined in the spec. +func NewGetClustersParams() GetClustersParams { + + return GetClustersParams{} +} + +// GetClustersParams contains all the bound params for the get clusters operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetClusters +type GetClustersParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Created at after this date + In: query + */ + CreatedAtFrom *strfmt.DateTime + /*Created at till this date + In: query + */ + CreatedAtTo *strfmt.DateTime + /*Filter by environment + In: query + */ + Environment *string + /* + In: query + */ + Limit *int64 + /*Filter by location + In: query + */ + Location *string + /*Filter by name + In: query + */ + Name *string + /* + In: query + */ + Offset *int64 + /*Filter by postgres_version + In: query + */ + PostgresVersion *int64 + /* + Required: true + In: query + */ + ProjectID int64 + /*Filter by server_count + In: query + */ + ServerCount *int64 + /*Sort by fields. Example: sort_by=id,-name,created_at,updated_at + Supported values: + - id + - name + - created_at + - updated_at + - environment + - project + - status + - location + - server_count + - postgres_version + + In: query + */ + SortBy *string + /*Filter by status + In: query + */ + Status *string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetClustersParams() beforehand. +func (o *GetClustersParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qCreatedAtFrom, qhkCreatedAtFrom, _ := qs.GetOK("created_at_from") + if err := o.bindCreatedAtFrom(qCreatedAtFrom, qhkCreatedAtFrom, route.Formats); err != nil { + res = append(res, err) + } + + qCreatedAtTo, qhkCreatedAtTo, _ := qs.GetOK("created_at_to") + if err := o.bindCreatedAtTo(qCreatedAtTo, qhkCreatedAtTo, route.Formats); err != nil { + res = append(res, err) + } + + qEnvironment, qhkEnvironment, _ := qs.GetOK("environment") + if err := o.bindEnvironment(qEnvironment, qhkEnvironment, route.Formats); err != nil { + res = append(res, err) + } + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qLocation, qhkLocation, _ := qs.GetOK("location") + if err := o.bindLocation(qLocation, qhkLocation, route.Formats); err != nil { + res = append(res, err) + } + + qName, qhkName, _ := qs.GetOK("name") + if err := o.bindName(qName, qhkName, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + + qPostgresVersion, qhkPostgresVersion, _ := qs.GetOK("postgres_version") + if err := o.bindPostgresVersion(qPostgresVersion, qhkPostgresVersion, route.Formats); err != nil { + res = append(res, err) + } + + qProjectID, qhkProjectID, _ := qs.GetOK("project_id") + if err := o.bindProjectID(qProjectID, qhkProjectID, route.Formats); err != nil { + res = append(res, err) + } + + qServerCount, qhkServerCount, _ := qs.GetOK("server_count") + if err := o.bindServerCount(qServerCount, qhkServerCount, route.Formats); err != nil { + res = append(res, err) + } + + qSortBy, qhkSortBy, _ := qs.GetOK("sort_by") + if err := o.bindSortBy(qSortBy, qhkSortBy, route.Formats); err != nil { + res = append(res, err) + } + + qStatus, qhkStatus, _ := qs.GetOK("status") + if err := o.bindStatus(qStatus, qhkStatus, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindCreatedAtFrom binds and validates parameter CreatedAtFrom from query. +func (o *GetClustersParams) bindCreatedAtFrom(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + // Format: date-time + value, err := formats.Parse("date-time", raw) + if err != nil { + return errors.InvalidType("created_at_from", "query", "strfmt.DateTime", raw) + } + o.CreatedAtFrom = (value.(*strfmt.DateTime)) + + if err := o.validateCreatedAtFrom(formats); err != nil { + return err + } + + return nil +} + +// validateCreatedAtFrom carries on validations for parameter CreatedAtFrom +func (o *GetClustersParams) validateCreatedAtFrom(formats strfmt.Registry) error { + + if err := validate.FormatOf("created_at_from", "query", "date-time", o.CreatedAtFrom.String(), formats); err != nil { + return err + } + return nil +} + +// bindCreatedAtTo binds and validates parameter CreatedAtTo from query. +func (o *GetClustersParams) bindCreatedAtTo(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + // Format: date-time + value, err := formats.Parse("date-time", raw) + if err != nil { + return errors.InvalidType("created_at_to", "query", "strfmt.DateTime", raw) + } + o.CreatedAtTo = (value.(*strfmt.DateTime)) + + if err := o.validateCreatedAtTo(formats); err != nil { + return err + } + + return nil +} + +// validateCreatedAtTo carries on validations for parameter CreatedAtTo +func (o *GetClustersParams) validateCreatedAtTo(formats strfmt.Registry) error { + + if err := validate.FormatOf("created_at_to", "query", "date-time", o.CreatedAtTo.String(), formats); err != nil { + return err + } + return nil +} + +// bindEnvironment binds and validates parameter Environment from query. +func (o *GetClustersParams) bindEnvironment(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Environment = &raw + + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetClustersParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindLocation binds and validates parameter Location from query. +func (o *GetClustersParams) bindLocation(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Location = &raw + + return nil +} + +// bindName binds and validates parameter Name from query. +func (o *GetClustersParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Name = &raw + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetClustersParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} + +// bindPostgresVersion binds and validates parameter PostgresVersion from query. +func (o *GetClustersParams) bindPostgresVersion(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("postgres_version", "query", "int64", raw) + } + o.PostgresVersion = &value + + return nil +} + +// bindProjectID binds and validates parameter ProjectID from query. +func (o *GetClustersParams) bindProjectID(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("project_id", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("project_id", "query", raw); err != nil { + return err + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("project_id", "query", "int64", raw) + } + o.ProjectID = value + + return nil +} + +// bindServerCount binds and validates parameter ServerCount from query. +func (o *GetClustersParams) bindServerCount(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("server_count", "query", "int64", raw) + } + o.ServerCount = &value + + return nil +} + +// bindSortBy binds and validates parameter SortBy from query. +func (o *GetClustersParams) bindSortBy(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.SortBy = &raw + + return nil +} + +// bindStatus binds and validates parameter Status from query. +func (o *GetClustersParams) bindStatus(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Status = &raw + + return nil +} diff --git a/console/service/restapi/operations/cluster/get_clusters_responses.go b/console/service/restapi/operations/cluster/get_clusters_responses.go new file mode 100644 index 000000000..a96a1352d --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetClustersOKCode is the HTTP code returned for type GetClustersOK +const GetClustersOKCode int = 200 + +/* +GetClustersOK OK + +swagger:response getClustersOK +*/ +type GetClustersOK struct { + + /* + In: Body + */ + Payload *models.ResponseClustersInfo `json:"body,omitempty"` +} + +// NewGetClustersOK creates GetClustersOK with default headers values +func NewGetClustersOK() *GetClustersOK { + + return &GetClustersOK{} +} + +// WithPayload adds the payload to the get clusters o k response +func (o *GetClustersOK) WithPayload(payload *models.ResponseClustersInfo) *GetClustersOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get clusters o k response +func (o *GetClustersOK) SetPayload(payload *models.ResponseClustersInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetClustersOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetClustersBadRequestCode is the HTTP code returned for type GetClustersBadRequest +const GetClustersBadRequestCode int = 400 + +/* +GetClustersBadRequest Error + +swagger:response getClustersBadRequest +*/ +type GetClustersBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetClustersBadRequest creates GetClustersBadRequest with default headers values +func NewGetClustersBadRequest() *GetClustersBadRequest { + + return &GetClustersBadRequest{} +} + +// WithPayload adds the payload to the get clusters bad request response +func (o *GetClustersBadRequest) WithPayload(payload *models.ResponseError) *GetClustersBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get clusters bad request response +func (o *GetClustersBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetClustersBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/get_clusters_urlbuilder.go b/console/service/restapi/operations/cluster/get_clusters_urlbuilder.go new file mode 100644 index 000000000..c8cc4d83d --- /dev/null +++ b/console/service/restapi/operations/cluster/get_clusters_urlbuilder.go @@ -0,0 +1,202 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// GetClustersURL generates an URL for the get clusters operation +type GetClustersURL struct { + CreatedAtFrom *strfmt.DateTime + CreatedAtTo *strfmt.DateTime + Environment *string + Limit *int64 + Location *string + Name *string + Offset *int64 + PostgresVersion *int64 + ProjectID int64 + ServerCount *int64 + SortBy *string + Status *string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetClustersURL) WithBasePath(bp string) *GetClustersURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetClustersURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetClustersURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var createdAtFromQ string + if o.CreatedAtFrom != nil { + createdAtFromQ = o.CreatedAtFrom.String() + } + if createdAtFromQ != "" { + qs.Set("created_at_from", createdAtFromQ) + } + + var createdAtToQ string + if o.CreatedAtTo != nil { + createdAtToQ = o.CreatedAtTo.String() + } + if createdAtToQ != "" { + qs.Set("created_at_to", createdAtToQ) + } + + var environmentQ string + if o.Environment != nil { + environmentQ = *o.Environment + } + if environmentQ != "" { + qs.Set("environment", environmentQ) + } + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var locationQ string + if o.Location != nil { + locationQ = *o.Location + } + if locationQ != "" { + qs.Set("location", locationQ) + } + + var nameQ string + if o.Name != nil { + nameQ = *o.Name + } + if nameQ != "" { + qs.Set("name", nameQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + var postgresVersionQ string + if o.PostgresVersion != nil { + postgresVersionQ = swag.FormatInt64(*o.PostgresVersion) + } + if postgresVersionQ != "" { + qs.Set("postgres_version", postgresVersionQ) + } + + projectIDQ := swag.FormatInt64(o.ProjectID) + if projectIDQ != "" { + qs.Set("project_id", projectIDQ) + } + + var serverCountQ string + if o.ServerCount != nil { + serverCountQ = swag.FormatInt64(*o.ServerCount) + } + if serverCountQ != "" { + qs.Set("server_count", serverCountQ) + } + + var sortByQ string + if o.SortBy != nil { + sortByQ = *o.SortBy + } + if sortByQ != "" { + qs.Set("sort_by", sortByQ) + } + + var statusQ string + if o.Status != nil { + statusQ = *o.Status + } + if statusQ != "" { + qs.Set("status", statusQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetClustersURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetClustersURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetClustersURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetClustersURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetClustersURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetClustersURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters.go b/console/service/restapi/operations/cluster/post_clusters.go new file mode 100644 index 000000000..f3ce7808a --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersHandlerFunc turns a function with the right signature into a post clusters handler +type PostClustersHandlerFunc func(PostClustersParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersHandlerFunc) Handle(params PostClustersParams) middleware.Responder { + return fn(params) +} + +// PostClustersHandler interface for that can handle valid post clusters params +type PostClustersHandler interface { + Handle(PostClustersParams) middleware.Responder +} + +// NewPostClusters creates a new http.Handler for the post clusters operation +func NewPostClusters(ctx *middleware.Context, handler PostClustersHandler) *PostClusters { + return &PostClusters{Context: ctx, Handler: handler} +} + +/* + PostClusters swagger:route POST /clusters cluster postClusters + +Create new cluster +*/ +type PostClusters struct { + Context *middleware.Context + Handler PostClustersHandler +} + +func (o *PostClusters) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_refresh.go b/console/service/restapi/operations/cluster/post_clusters_id_refresh.go new file mode 100644 index 000000000..994b4ed93 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_refresh.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersIDRefreshHandlerFunc turns a function with the right signature into a post clusters ID refresh handler +type PostClustersIDRefreshHandlerFunc func(PostClustersIDRefreshParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersIDRefreshHandlerFunc) Handle(params PostClustersIDRefreshParams) middleware.Responder { + return fn(params) +} + +// PostClustersIDRefreshHandler interface for that can handle valid post clusters ID refresh params +type PostClustersIDRefreshHandler interface { + Handle(PostClustersIDRefreshParams) middleware.Responder +} + +// NewPostClustersIDRefresh creates a new http.Handler for the post clusters ID refresh operation +func NewPostClustersIDRefresh(ctx *middleware.Context, handler PostClustersIDRefreshHandler) *PostClustersIDRefresh { + return &PostClustersIDRefresh{Context: ctx, Handler: handler} +} + +/* + PostClustersIDRefresh swagger:route POST /clusters/{id}/refresh cluster postClustersIdRefresh + +Refresh cluster info +*/ +type PostClustersIDRefresh struct { + Context *middleware.Context + Handler PostClustersIDRefreshHandler +} + +func (o *PostClustersIDRefresh) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersIDRefreshParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_refresh_parameters.go b/console/service/restapi/operations/cluster/post_clusters_id_refresh_parameters.go new file mode 100644 index 000000000..fd8c54d2d --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_refresh_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewPostClustersIDRefreshParams creates a new PostClustersIDRefreshParams object +// +// There are no default values defined in the spec. +func NewPostClustersIDRefreshParams() PostClustersIDRefreshParams { + + return PostClustersIDRefreshParams{} +} + +// PostClustersIDRefreshParams contains all the bound params for the post clusters ID refresh operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClustersIDRefresh +type PostClustersIDRefreshParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersIDRefreshParams() beforehand. +func (o *PostClustersIDRefreshParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PostClustersIDRefreshParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_refresh_responses.go b/console/service/restapi/operations/cluster/post_clusters_id_refresh_responses.go new file mode 100644 index 000000000..d79ac9495 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_refresh_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersIDRefreshOKCode is the HTTP code returned for type PostClustersIDRefreshOK +const PostClustersIDRefreshOKCode int = 200 + +/* +PostClustersIDRefreshOK OK + +swagger:response postClustersIdRefreshOK +*/ +type PostClustersIDRefreshOK struct { + + /* + In: Body + */ + Payload *models.ClusterInfo `json:"body,omitempty"` +} + +// NewPostClustersIDRefreshOK creates PostClustersIDRefreshOK with default headers values +func NewPostClustersIDRefreshOK() *PostClustersIDRefreshOK { + + return &PostClustersIDRefreshOK{} +} + +// WithPayload adds the payload to the post clusters Id refresh o k response +func (o *PostClustersIDRefreshOK) WithPayload(payload *models.ClusterInfo) *PostClustersIDRefreshOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id refresh o k response +func (o *PostClustersIDRefreshOK) SetPayload(payload *models.ClusterInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDRefreshOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostClustersIDRefreshBadRequestCode is the HTTP code returned for type PostClustersIDRefreshBadRequest +const PostClustersIDRefreshBadRequestCode int = 400 + +/* +PostClustersIDRefreshBadRequest Error + +swagger:response postClustersIdRefreshBadRequest +*/ +type PostClustersIDRefreshBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersIDRefreshBadRequest creates PostClustersIDRefreshBadRequest with default headers values +func NewPostClustersIDRefreshBadRequest() *PostClustersIDRefreshBadRequest { + + return &PostClustersIDRefreshBadRequest{} +} + +// WithPayload adds the payload to the post clusters Id refresh bad request response +func (o *PostClustersIDRefreshBadRequest) WithPayload(payload *models.ResponseError) *PostClustersIDRefreshBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id refresh bad request response +func (o *PostClustersIDRefreshBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDRefreshBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_refresh_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_id_refresh_urlbuilder.go new file mode 100644 index 000000000..bdd294411 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_refresh_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PostClustersIDRefreshURL generates an URL for the post clusters ID refresh operation +type PostClustersIDRefreshURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDRefreshURL) WithBasePath(bp string) *PostClustersIDRefreshURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDRefreshURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersIDRefreshURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}/refresh" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PostClustersIDRefreshURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersIDRefreshURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersIDRefreshURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersIDRefreshURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersIDRefreshURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersIDRefreshURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersIDRefreshURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reinit.go b/console/service/restapi/operations/cluster/post_clusters_id_reinit.go new file mode 100644 index 000000000..a0947aa32 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reinit.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersIDReinitHandlerFunc turns a function with the right signature into a post clusters ID reinit handler +type PostClustersIDReinitHandlerFunc func(PostClustersIDReinitParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersIDReinitHandlerFunc) Handle(params PostClustersIDReinitParams) middleware.Responder { + return fn(params) +} + +// PostClustersIDReinitHandler interface for that can handle valid post clusters ID reinit params +type PostClustersIDReinitHandler interface { + Handle(PostClustersIDReinitParams) middleware.Responder +} + +// NewPostClustersIDReinit creates a new http.Handler for the post clusters ID reinit operation +func NewPostClustersIDReinit(ctx *middleware.Context, handler PostClustersIDReinitHandler) *PostClustersIDReinit { + return &PostClustersIDReinit{Context: ctx, Handler: handler} +} + +/* + PostClustersIDReinit swagger:route POST /clusters/{id}/reinit cluster postClustersIdReinit + +Reinit cluster +*/ +type PostClustersIDReinit struct { + Context *middleware.Context + Handler PostClustersIDReinitHandler +} + +func (o *PostClustersIDReinit) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersIDReinitParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reinit_parameters.go b/console/service/restapi/operations/cluster/post_clusters_id_reinit_parameters.go new file mode 100644 index 000000000..4b26474f9 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reinit_parameters.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// NewPostClustersIDReinitParams creates a new PostClustersIDReinitParams object +// +// There are no default values defined in the spec. +func NewPostClustersIDReinitParams() PostClustersIDReinitParams { + + return PostClustersIDReinitParams{} +} + +// PostClustersIDReinitParams contains all the bound params for the post clusters ID reinit operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClustersIDReinit +type PostClustersIDReinitParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body models.RequestClusterReinit + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersIDReinitParams() beforehand. +func (o *PostClustersIDReinitParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestClusterReinit + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // no validation on generic interface + o.Body = body + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PostClustersIDReinitParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reinit_responses.go b/console/service/restapi/operations/cluster/post_clusters_id_reinit_responses.go new file mode 100644 index 000000000..a0647d90d --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reinit_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersIDReinitOKCode is the HTTP code returned for type PostClustersIDReinitOK +const PostClustersIDReinitOKCode int = 200 + +/* +PostClustersIDReinitOK OK + +swagger:response postClustersIdReinitOK +*/ +type PostClustersIDReinitOK struct { + + /* + In: Body + */ + Payload *models.ResponseClusterCreate `json:"body,omitempty"` +} + +// NewPostClustersIDReinitOK creates PostClustersIDReinitOK with default headers values +func NewPostClustersIDReinitOK() *PostClustersIDReinitOK { + + return &PostClustersIDReinitOK{} +} + +// WithPayload adds the payload to the post clusters Id reinit o k response +func (o *PostClustersIDReinitOK) WithPayload(payload *models.ResponseClusterCreate) *PostClustersIDReinitOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id reinit o k response +func (o *PostClustersIDReinitOK) SetPayload(payload *models.ResponseClusterCreate) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDReinitOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostClustersIDReinitBadRequestCode is the HTTP code returned for type PostClustersIDReinitBadRequest +const PostClustersIDReinitBadRequestCode int = 400 + +/* +PostClustersIDReinitBadRequest Error + +swagger:response postClustersIdReinitBadRequest +*/ +type PostClustersIDReinitBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersIDReinitBadRequest creates PostClustersIDReinitBadRequest with default headers values +func NewPostClustersIDReinitBadRequest() *PostClustersIDReinitBadRequest { + + return &PostClustersIDReinitBadRequest{} +} + +// WithPayload adds the payload to the post clusters Id reinit bad request response +func (o *PostClustersIDReinitBadRequest) WithPayload(payload *models.ResponseError) *PostClustersIDReinitBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id reinit bad request response +func (o *PostClustersIDReinitBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDReinitBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reinit_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_id_reinit_urlbuilder.go new file mode 100644 index 000000000..a522a85ce --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reinit_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PostClustersIDReinitURL generates an URL for the post clusters ID reinit operation +type PostClustersIDReinitURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDReinitURL) WithBasePath(bp string) *PostClustersIDReinitURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDReinitURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersIDReinitURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}/reinit" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PostClustersIDReinitURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersIDReinitURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersIDReinitURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersIDReinitURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersIDReinitURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersIDReinitURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersIDReinitURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reload.go b/console/service/restapi/operations/cluster/post_clusters_id_reload.go new file mode 100644 index 000000000..9de7282ad --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reload.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersIDReloadHandlerFunc turns a function with the right signature into a post clusters ID reload handler +type PostClustersIDReloadHandlerFunc func(PostClustersIDReloadParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersIDReloadHandlerFunc) Handle(params PostClustersIDReloadParams) middleware.Responder { + return fn(params) +} + +// PostClustersIDReloadHandler interface for that can handle valid post clusters ID reload params +type PostClustersIDReloadHandler interface { + Handle(PostClustersIDReloadParams) middleware.Responder +} + +// NewPostClustersIDReload creates a new http.Handler for the post clusters ID reload operation +func NewPostClustersIDReload(ctx *middleware.Context, handler PostClustersIDReloadHandler) *PostClustersIDReload { + return &PostClustersIDReload{Context: ctx, Handler: handler} +} + +/* + PostClustersIDReload swagger:route POST /clusters/{id}/reload cluster postClustersIdReload + +Reload cluster +*/ +type PostClustersIDReload struct { + Context *middleware.Context + Handler PostClustersIDReloadHandler +} + +func (o *PostClustersIDReload) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersIDReloadParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reload_parameters.go b/console/service/restapi/operations/cluster/post_clusters_id_reload_parameters.go new file mode 100644 index 000000000..5b42b9c46 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reload_parameters.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// NewPostClustersIDReloadParams creates a new PostClustersIDReloadParams object +// +// There are no default values defined in the spec. +func NewPostClustersIDReloadParams() PostClustersIDReloadParams { + + return PostClustersIDReloadParams{} +} + +// PostClustersIDReloadParams contains all the bound params for the post clusters ID reload operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClustersIDReload +type PostClustersIDReloadParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body models.RequestClusterReload + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersIDReloadParams() beforehand. +func (o *PostClustersIDReloadParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestClusterReload + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // no validation on generic interface + o.Body = body + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PostClustersIDReloadParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reload_responses.go b/console/service/restapi/operations/cluster/post_clusters_id_reload_responses.go new file mode 100644 index 000000000..bc09e9e21 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reload_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersIDReloadOKCode is the HTTP code returned for type PostClustersIDReloadOK +const PostClustersIDReloadOKCode int = 200 + +/* +PostClustersIDReloadOK OK + +swagger:response postClustersIdReloadOK +*/ +type PostClustersIDReloadOK struct { + + /* + In: Body + */ + Payload *models.ResponseClusterCreate `json:"body,omitempty"` +} + +// NewPostClustersIDReloadOK creates PostClustersIDReloadOK with default headers values +func NewPostClustersIDReloadOK() *PostClustersIDReloadOK { + + return &PostClustersIDReloadOK{} +} + +// WithPayload adds the payload to the post clusters Id reload o k response +func (o *PostClustersIDReloadOK) WithPayload(payload *models.ResponseClusterCreate) *PostClustersIDReloadOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id reload o k response +func (o *PostClustersIDReloadOK) SetPayload(payload *models.ResponseClusterCreate) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDReloadOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostClustersIDReloadBadRequestCode is the HTTP code returned for type PostClustersIDReloadBadRequest +const PostClustersIDReloadBadRequestCode int = 400 + +/* +PostClustersIDReloadBadRequest Error + +swagger:response postClustersIdReloadBadRequest +*/ +type PostClustersIDReloadBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersIDReloadBadRequest creates PostClustersIDReloadBadRequest with default headers values +func NewPostClustersIDReloadBadRequest() *PostClustersIDReloadBadRequest { + + return &PostClustersIDReloadBadRequest{} +} + +// WithPayload adds the payload to the post clusters Id reload bad request response +func (o *PostClustersIDReloadBadRequest) WithPayload(payload *models.ResponseError) *PostClustersIDReloadBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id reload bad request response +func (o *PostClustersIDReloadBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDReloadBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_reload_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_id_reload_urlbuilder.go new file mode 100644 index 000000000..bed96c058 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_reload_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PostClustersIDReloadURL generates an URL for the post clusters ID reload operation +type PostClustersIDReloadURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDReloadURL) WithBasePath(bp string) *PostClustersIDReloadURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDReloadURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersIDReloadURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}/reload" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PostClustersIDReloadURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersIDReloadURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersIDReloadURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersIDReloadURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersIDReloadURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersIDReloadURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersIDReloadURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_remove.go b/console/service/restapi/operations/cluster/post_clusters_id_remove.go new file mode 100644 index 000000000..147528fe4 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_remove.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersIDRemoveHandlerFunc turns a function with the right signature into a post clusters ID remove handler +type PostClustersIDRemoveHandlerFunc func(PostClustersIDRemoveParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersIDRemoveHandlerFunc) Handle(params PostClustersIDRemoveParams) middleware.Responder { + return fn(params) +} + +// PostClustersIDRemoveHandler interface for that can handle valid post clusters ID remove params +type PostClustersIDRemoveHandler interface { + Handle(PostClustersIDRemoveParams) middleware.Responder +} + +// NewPostClustersIDRemove creates a new http.Handler for the post clusters ID remove operation +func NewPostClustersIDRemove(ctx *middleware.Context, handler PostClustersIDRemoveHandler) *PostClustersIDRemove { + return &PostClustersIDRemove{Context: ctx, Handler: handler} +} + +/* + PostClustersIDRemove swagger:route POST /clusters/{id}/remove cluster postClustersIdRemove + +Remove cluster +*/ +type PostClustersIDRemove struct { + Context *middleware.Context + Handler PostClustersIDRemoveHandler +} + +func (o *PostClustersIDRemove) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersIDRemoveParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_remove_parameters.go b/console/service/restapi/operations/cluster/post_clusters_id_remove_parameters.go new file mode 100644 index 000000000..c15cdc122 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_remove_parameters.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// NewPostClustersIDRemoveParams creates a new PostClustersIDRemoveParams object +// +// There are no default values defined in the spec. +func NewPostClustersIDRemoveParams() PostClustersIDRemoveParams { + + return PostClustersIDRemoveParams{} +} + +// PostClustersIDRemoveParams contains all the bound params for the post clusters ID remove operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClustersIDRemove +type PostClustersIDRemoveParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body models.RequestClusterRemove + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersIDRemoveParams() beforehand. +func (o *PostClustersIDRemoveParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestClusterRemove + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // no validation on generic interface + o.Body = body + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PostClustersIDRemoveParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_remove_responses.go b/console/service/restapi/operations/cluster/post_clusters_id_remove_responses.go new file mode 100644 index 000000000..cb659290d --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_remove_responses.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersIDRemoveNoContentCode is the HTTP code returned for type PostClustersIDRemoveNoContent +const PostClustersIDRemoveNoContentCode int = 204 + +/* +PostClustersIDRemoveNoContent OK + +swagger:response postClustersIdRemoveNoContent +*/ +type PostClustersIDRemoveNoContent struct { +} + +// NewPostClustersIDRemoveNoContent creates PostClustersIDRemoveNoContent with default headers values +func NewPostClustersIDRemoveNoContent() *PostClustersIDRemoveNoContent { + + return &PostClustersIDRemoveNoContent{} +} + +// WriteResponse to the client +func (o *PostClustersIDRemoveNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +// PostClustersIDRemoveBadRequestCode is the HTTP code returned for type PostClustersIDRemoveBadRequest +const PostClustersIDRemoveBadRequestCode int = 400 + +/* +PostClustersIDRemoveBadRequest Error + +swagger:response postClustersIdRemoveBadRequest +*/ +type PostClustersIDRemoveBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersIDRemoveBadRequest creates PostClustersIDRemoveBadRequest with default headers values +func NewPostClustersIDRemoveBadRequest() *PostClustersIDRemoveBadRequest { + + return &PostClustersIDRemoveBadRequest{} +} + +// WithPayload adds the payload to the post clusters Id remove bad request response +func (o *PostClustersIDRemoveBadRequest) WithPayload(payload *models.ResponseError) *PostClustersIDRemoveBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id remove bad request response +func (o *PostClustersIDRemoveBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDRemoveBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_remove_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_id_remove_urlbuilder.go new file mode 100644 index 000000000..3688dbb60 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_remove_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PostClustersIDRemoveURL generates an URL for the post clusters ID remove operation +type PostClustersIDRemoveURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDRemoveURL) WithBasePath(bp string) *PostClustersIDRemoveURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDRemoveURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersIDRemoveURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}/remove" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PostClustersIDRemoveURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersIDRemoveURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersIDRemoveURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersIDRemoveURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersIDRemoveURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersIDRemoveURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersIDRemoveURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_restart.go b/console/service/restapi/operations/cluster/post_clusters_id_restart.go new file mode 100644 index 000000000..405fd5ecb --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_restart.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersIDRestartHandlerFunc turns a function with the right signature into a post clusters ID restart handler +type PostClustersIDRestartHandlerFunc func(PostClustersIDRestartParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersIDRestartHandlerFunc) Handle(params PostClustersIDRestartParams) middleware.Responder { + return fn(params) +} + +// PostClustersIDRestartHandler interface for that can handle valid post clusters ID restart params +type PostClustersIDRestartHandler interface { + Handle(PostClustersIDRestartParams) middleware.Responder +} + +// NewPostClustersIDRestart creates a new http.Handler for the post clusters ID restart operation +func NewPostClustersIDRestart(ctx *middleware.Context, handler PostClustersIDRestartHandler) *PostClustersIDRestart { + return &PostClustersIDRestart{Context: ctx, Handler: handler} +} + +/* + PostClustersIDRestart swagger:route POST /clusters/{id}/restart cluster postClustersIdRestart + +Restart cluster +*/ +type PostClustersIDRestart struct { + Context *middleware.Context + Handler PostClustersIDRestartHandler +} + +func (o *PostClustersIDRestart) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersIDRestartParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_restart_parameters.go b/console/service/restapi/operations/cluster/post_clusters_id_restart_parameters.go new file mode 100644 index 000000000..843f42410 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_restart_parameters.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// NewPostClustersIDRestartParams creates a new PostClustersIDRestartParams object +// +// There are no default values defined in the spec. +func NewPostClustersIDRestartParams() PostClustersIDRestartParams { + + return PostClustersIDRestartParams{} +} + +// PostClustersIDRestartParams contains all the bound params for the post clusters ID restart operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClustersIDRestart +type PostClustersIDRestartParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body models.RequestClusterRestart + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersIDRestartParams() beforehand. +func (o *PostClustersIDRestartParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestClusterRestart + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // no validation on generic interface + o.Body = body + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PostClustersIDRestartParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_restart_responses.go b/console/service/restapi/operations/cluster/post_clusters_id_restart_responses.go new file mode 100644 index 000000000..d50ba3a08 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_restart_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersIDRestartOKCode is the HTTP code returned for type PostClustersIDRestartOK +const PostClustersIDRestartOKCode int = 200 + +/* +PostClustersIDRestartOK OK + +swagger:response postClustersIdRestartOK +*/ +type PostClustersIDRestartOK struct { + + /* + In: Body + */ + Payload *models.ResponseClusterCreate `json:"body,omitempty"` +} + +// NewPostClustersIDRestartOK creates PostClustersIDRestartOK with default headers values +func NewPostClustersIDRestartOK() *PostClustersIDRestartOK { + + return &PostClustersIDRestartOK{} +} + +// WithPayload adds the payload to the post clusters Id restart o k response +func (o *PostClustersIDRestartOK) WithPayload(payload *models.ResponseClusterCreate) *PostClustersIDRestartOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id restart o k response +func (o *PostClustersIDRestartOK) SetPayload(payload *models.ResponseClusterCreate) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDRestartOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostClustersIDRestartBadRequestCode is the HTTP code returned for type PostClustersIDRestartBadRequest +const PostClustersIDRestartBadRequestCode int = 400 + +/* +PostClustersIDRestartBadRequest Error + +swagger:response postClustersIdRestartBadRequest +*/ +type PostClustersIDRestartBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersIDRestartBadRequest creates PostClustersIDRestartBadRequest with default headers values +func NewPostClustersIDRestartBadRequest() *PostClustersIDRestartBadRequest { + + return &PostClustersIDRestartBadRequest{} +} + +// WithPayload adds the payload to the post clusters Id restart bad request response +func (o *PostClustersIDRestartBadRequest) WithPayload(payload *models.ResponseError) *PostClustersIDRestartBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id restart bad request response +func (o *PostClustersIDRestartBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDRestartBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_restart_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_id_restart_urlbuilder.go new file mode 100644 index 000000000..fae9b14ab --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_restart_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PostClustersIDRestartURL generates an URL for the post clusters ID restart operation +type PostClustersIDRestartURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDRestartURL) WithBasePath(bp string) *PostClustersIDRestartURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDRestartURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersIDRestartURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}/restart" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PostClustersIDRestartURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersIDRestartURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersIDRestartURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersIDRestartURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersIDRestartURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersIDRestartURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersIDRestartURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_start.go b/console/service/restapi/operations/cluster/post_clusters_id_start.go new file mode 100644 index 000000000..ff3adf033 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_start.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersIDStartHandlerFunc turns a function with the right signature into a post clusters ID start handler +type PostClustersIDStartHandlerFunc func(PostClustersIDStartParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersIDStartHandlerFunc) Handle(params PostClustersIDStartParams) middleware.Responder { + return fn(params) +} + +// PostClustersIDStartHandler interface for that can handle valid post clusters ID start params +type PostClustersIDStartHandler interface { + Handle(PostClustersIDStartParams) middleware.Responder +} + +// NewPostClustersIDStart creates a new http.Handler for the post clusters ID start operation +func NewPostClustersIDStart(ctx *middleware.Context, handler PostClustersIDStartHandler) *PostClustersIDStart { + return &PostClustersIDStart{Context: ctx, Handler: handler} +} + +/* + PostClustersIDStart swagger:route POST /clusters/{id}/start cluster postClustersIdStart + +Start cluster +*/ +type PostClustersIDStart struct { + Context *middleware.Context + Handler PostClustersIDStartHandler +} + +func (o *PostClustersIDStart) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersIDStartParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_start_parameters.go b/console/service/restapi/operations/cluster/post_clusters_id_start_parameters.go new file mode 100644 index 000000000..aa477070e --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_start_parameters.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// NewPostClustersIDStartParams creates a new PostClustersIDStartParams object +// +// There are no default values defined in the spec. +func NewPostClustersIDStartParams() PostClustersIDStartParams { + + return PostClustersIDStartParams{} +} + +// PostClustersIDStartParams contains all the bound params for the post clusters ID start operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClustersIDStart +type PostClustersIDStartParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body models.RequestClusterStart + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersIDStartParams() beforehand. +func (o *PostClustersIDStartParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestClusterStart + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // no validation on generic interface + o.Body = body + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PostClustersIDStartParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_start_responses.go b/console/service/restapi/operations/cluster/post_clusters_id_start_responses.go new file mode 100644 index 000000000..7d3a6105d --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_start_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersIDStartOKCode is the HTTP code returned for type PostClustersIDStartOK +const PostClustersIDStartOKCode int = 200 + +/* +PostClustersIDStartOK OK + +swagger:response postClustersIdStartOK +*/ +type PostClustersIDStartOK struct { + + /* + In: Body + */ + Payload *models.ResponseClusterCreate `json:"body,omitempty"` +} + +// NewPostClustersIDStartOK creates PostClustersIDStartOK with default headers values +func NewPostClustersIDStartOK() *PostClustersIDStartOK { + + return &PostClustersIDStartOK{} +} + +// WithPayload adds the payload to the post clusters Id start o k response +func (o *PostClustersIDStartOK) WithPayload(payload *models.ResponseClusterCreate) *PostClustersIDStartOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id start o k response +func (o *PostClustersIDStartOK) SetPayload(payload *models.ResponseClusterCreate) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDStartOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostClustersIDStartBadRequestCode is the HTTP code returned for type PostClustersIDStartBadRequest +const PostClustersIDStartBadRequestCode int = 400 + +/* +PostClustersIDStartBadRequest Error + +swagger:response postClustersIdStartBadRequest +*/ +type PostClustersIDStartBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersIDStartBadRequest creates PostClustersIDStartBadRequest with default headers values +func NewPostClustersIDStartBadRequest() *PostClustersIDStartBadRequest { + + return &PostClustersIDStartBadRequest{} +} + +// WithPayload adds the payload to the post clusters Id start bad request response +func (o *PostClustersIDStartBadRequest) WithPayload(payload *models.ResponseError) *PostClustersIDStartBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id start bad request response +func (o *PostClustersIDStartBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDStartBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_start_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_id_start_urlbuilder.go new file mode 100644 index 000000000..96efd6fed --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_start_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PostClustersIDStartURL generates an URL for the post clusters ID start operation +type PostClustersIDStartURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDStartURL) WithBasePath(bp string) *PostClustersIDStartURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDStartURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersIDStartURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}/start" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PostClustersIDStartURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersIDStartURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersIDStartURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersIDStartURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersIDStartURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersIDStartURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersIDStartURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_stop.go b/console/service/restapi/operations/cluster/post_clusters_id_stop.go new file mode 100644 index 000000000..2a94cd3d9 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_stop.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostClustersIDStopHandlerFunc turns a function with the right signature into a post clusters ID stop handler +type PostClustersIDStopHandlerFunc func(PostClustersIDStopParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostClustersIDStopHandlerFunc) Handle(params PostClustersIDStopParams) middleware.Responder { + return fn(params) +} + +// PostClustersIDStopHandler interface for that can handle valid post clusters ID stop params +type PostClustersIDStopHandler interface { + Handle(PostClustersIDStopParams) middleware.Responder +} + +// NewPostClustersIDStop creates a new http.Handler for the post clusters ID stop operation +func NewPostClustersIDStop(ctx *middleware.Context, handler PostClustersIDStopHandler) *PostClustersIDStop { + return &PostClustersIDStop{Context: ctx, Handler: handler} +} + +/* + PostClustersIDStop swagger:route POST /clusters/{id}/stop cluster postClustersIdStop + +Stop cluster +*/ +type PostClustersIDStop struct { + Context *middleware.Context + Handler PostClustersIDStopHandler +} + +func (o *PostClustersIDStop) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostClustersIDStopParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_stop_parameters.go b/console/service/restapi/operations/cluster/post_clusters_id_stop_parameters.go new file mode 100644 index 000000000..814b9ecb1 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_stop_parameters.go @@ -0,0 +1,103 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// NewPostClustersIDStopParams creates a new PostClustersIDStopParams object +// +// There are no default values defined in the spec. +func NewPostClustersIDStopParams() PostClustersIDStopParams { + + return PostClustersIDStopParams{} +} + +// PostClustersIDStopParams contains all the bound params for the post clusters ID stop operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClustersIDStop +type PostClustersIDStopParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body models.RequestClusterStop + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersIDStopParams() beforehand. +func (o *PostClustersIDStopParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestClusterStop + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // no validation on generic interface + o.Body = body + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PostClustersIDStopParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_stop_responses.go b/console/service/restapi/operations/cluster/post_clusters_id_stop_responses.go new file mode 100644 index 000000000..204fd9675 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_stop_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersIDStopOKCode is the HTTP code returned for type PostClustersIDStopOK +const PostClustersIDStopOKCode int = 200 + +/* +PostClustersIDStopOK OK + +swagger:response postClustersIdStopOK +*/ +type PostClustersIDStopOK struct { + + /* + In: Body + */ + Payload *models.ResponseClusterCreate `json:"body,omitempty"` +} + +// NewPostClustersIDStopOK creates PostClustersIDStopOK with default headers values +func NewPostClustersIDStopOK() *PostClustersIDStopOK { + + return &PostClustersIDStopOK{} +} + +// WithPayload adds the payload to the post clusters Id stop o k response +func (o *PostClustersIDStopOK) WithPayload(payload *models.ResponseClusterCreate) *PostClustersIDStopOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id stop o k response +func (o *PostClustersIDStopOK) SetPayload(payload *models.ResponseClusterCreate) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDStopOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostClustersIDStopBadRequestCode is the HTTP code returned for type PostClustersIDStopBadRequest +const PostClustersIDStopBadRequestCode int = 400 + +/* +PostClustersIDStopBadRequest Error + +swagger:response postClustersIdStopBadRequest +*/ +type PostClustersIDStopBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersIDStopBadRequest creates PostClustersIDStopBadRequest with default headers values +func NewPostClustersIDStopBadRequest() *PostClustersIDStopBadRequest { + + return &PostClustersIDStopBadRequest{} +} + +// WithPayload adds the payload to the post clusters Id stop bad request response +func (o *PostClustersIDStopBadRequest) WithPayload(payload *models.ResponseError) *PostClustersIDStopBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters Id stop bad request response +func (o *PostClustersIDStopBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersIDStopBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_id_stop_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_id_stop_urlbuilder.go new file mode 100644 index 000000000..aaabb6fc1 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_id_stop_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PostClustersIDStopURL generates an URL for the post clusters ID stop operation +type PostClustersIDStopURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDStopURL) WithBasePath(bp string) *PostClustersIDStopURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersIDStopURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersIDStopURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters/{id}/stop" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PostClustersIDStopURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersIDStopURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersIDStopURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersIDStopURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersIDStopURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersIDStopURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersIDStopURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/cluster/post_clusters_parameters.go b/console/service/restapi/operations/cluster/post_clusters_parameters.go new file mode 100644 index 000000000..6db1a8e17 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_parameters.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPostClustersParams creates a new PostClustersParams object +// +// There are no default values defined in the spec. +func NewPostClustersParams() PostClustersParams { + + return PostClustersParams{} +} + +// PostClustersParams contains all the bound params for the post clusters operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostClusters +type PostClustersParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestClusterCreate +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostClustersParams() beforehand. +func (o *PostClustersParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestClusterCreate + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/cluster/post_clusters_responses.go b/console/service/restapi/operations/cluster/post_clusters_responses.go new file mode 100644 index 000000000..57c05aa8b --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostClustersOKCode is the HTTP code returned for type PostClustersOK +const PostClustersOKCode int = 200 + +/* +PostClustersOK OK + +swagger:response postClustersOK +*/ +type PostClustersOK struct { + + /* + In: Body + */ + Payload *models.ResponseClusterCreate `json:"body,omitempty"` +} + +// NewPostClustersOK creates PostClustersOK with default headers values +func NewPostClustersOK() *PostClustersOK { + + return &PostClustersOK{} +} + +// WithPayload adds the payload to the post clusters o k response +func (o *PostClustersOK) WithPayload(payload *models.ResponseClusterCreate) *PostClustersOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters o k response +func (o *PostClustersOK) SetPayload(payload *models.ResponseClusterCreate) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostClustersBadRequestCode is the HTTP code returned for type PostClustersBadRequest +const PostClustersBadRequestCode int = 400 + +/* +PostClustersBadRequest Error + +swagger:response postClustersBadRequest +*/ +type PostClustersBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostClustersBadRequest creates PostClustersBadRequest with default headers values +func NewPostClustersBadRequest() *PostClustersBadRequest { + + return &PostClustersBadRequest{} +} + +// WithPayload adds the payload to the post clusters bad request response +func (o *PostClustersBadRequest) WithPayload(payload *models.ResponseError) *PostClustersBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post clusters bad request response +func (o *PostClustersBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostClustersBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/cluster/post_clusters_urlbuilder.go b/console/service/restapi/operations/cluster/post_clusters_urlbuilder.go new file mode 100644 index 000000000..072ae3210 --- /dev/null +++ b/console/service/restapi/operations/cluster/post_clusters_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package cluster + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// PostClustersURL generates an URL for the post clusters operation +type PostClustersURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersURL) WithBasePath(bp string) *PostClustersURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostClustersURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostClustersURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/clusters" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostClustersURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostClustersURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostClustersURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostClustersURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostClustersURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostClustersURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/dictionary/get_database_extensions.go b/console/service/restapi/operations/dictionary/get_database_extensions.go new file mode 100644 index 000000000..9985a5a78 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_database_extensions.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetDatabaseExtensionsHandlerFunc turns a function with the right signature into a get database extensions handler +type GetDatabaseExtensionsHandlerFunc func(GetDatabaseExtensionsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetDatabaseExtensionsHandlerFunc) Handle(params GetDatabaseExtensionsParams) middleware.Responder { + return fn(params) +} + +// GetDatabaseExtensionsHandler interface for that can handle valid get database extensions params +type GetDatabaseExtensionsHandler interface { + Handle(GetDatabaseExtensionsParams) middleware.Responder +} + +// NewGetDatabaseExtensions creates a new http.Handler for the get database extensions operation +func NewGetDatabaseExtensions(ctx *middleware.Context, handler GetDatabaseExtensionsHandler) *GetDatabaseExtensions { + return &GetDatabaseExtensions{Context: ctx, Handler: handler} +} + +/* + GetDatabaseExtensions swagger:route GET /database/extensions dictionary getDatabaseExtensions + +Info about available database extensions +*/ +type GetDatabaseExtensions struct { + Context *middleware.Context + Handler GetDatabaseExtensionsHandler +} + +func (o *GetDatabaseExtensions) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetDatabaseExtensionsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/dictionary/get_database_extensions_parameters.go b/console/service/restapi/operations/dictionary/get_database_extensions_parameters.go new file mode 100644 index 000000000..251902ec3 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_database_extensions_parameters.go @@ -0,0 +1,193 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewGetDatabaseExtensionsParams creates a new GetDatabaseExtensionsParams object +// with the default values initialized. +func NewGetDatabaseExtensionsParams() GetDatabaseExtensionsParams { + + var ( + // initialize parameters with default values + + extensionTypeDefault = string("all") + ) + + return GetDatabaseExtensionsParams{ + ExtensionType: &extensionTypeDefault, + } +} + +// GetDatabaseExtensionsParams contains all the bound params for the get database extensions operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetDatabaseExtensions +type GetDatabaseExtensionsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + Default: "all" + */ + ExtensionType *string + /* + In: query + */ + Limit *int64 + /* + In: query + */ + Offset *int64 + /* + In: query + */ + PostgresVersion *string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetDatabaseExtensionsParams() beforehand. +func (o *GetDatabaseExtensionsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qExtensionType, qhkExtensionType, _ := qs.GetOK("extension_type") + if err := o.bindExtensionType(qExtensionType, qhkExtensionType, route.Formats); err != nil { + res = append(res, err) + } + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + + qPostgresVersion, qhkPostgresVersion, _ := qs.GetOK("postgres_version") + if err := o.bindPostgresVersion(qPostgresVersion, qhkPostgresVersion, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindExtensionType binds and validates parameter ExtensionType from query. +func (o *GetDatabaseExtensionsParams) bindExtensionType(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetDatabaseExtensionsParams() + return nil + } + o.ExtensionType = &raw + + if err := o.validateExtensionType(formats); err != nil { + return err + } + + return nil +} + +// validateExtensionType carries on validations for parameter ExtensionType +func (o *GetDatabaseExtensionsParams) validateExtensionType(formats strfmt.Registry) error { + + if err := validate.EnumCase("extension_type", "query", *o.ExtensionType, []interface{}{"all", "contrib", "third_party"}, true); err != nil { + return err + } + + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetDatabaseExtensionsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetDatabaseExtensionsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} + +// bindPostgresVersion binds and validates parameter PostgresVersion from query. +func (o *GetDatabaseExtensionsParams) bindPostgresVersion(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.PostgresVersion = &raw + + return nil +} diff --git a/console/service/restapi/operations/dictionary/get_database_extensions_responses.go b/console/service/restapi/operations/dictionary/get_database_extensions_responses.go new file mode 100644 index 000000000..c4f3d8c3b --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_database_extensions_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetDatabaseExtensionsOKCode is the HTTP code returned for type GetDatabaseExtensionsOK +const GetDatabaseExtensionsOKCode int = 200 + +/* +GetDatabaseExtensionsOK OK + +swagger:response getDatabaseExtensionsOK +*/ +type GetDatabaseExtensionsOK struct { + + /* + In: Body + */ + Payload *models.ResponseDatabaseExtensions `json:"body,omitempty"` +} + +// NewGetDatabaseExtensionsOK creates GetDatabaseExtensionsOK with default headers values +func NewGetDatabaseExtensionsOK() *GetDatabaseExtensionsOK { + + return &GetDatabaseExtensionsOK{} +} + +// WithPayload adds the payload to the get database extensions o k response +func (o *GetDatabaseExtensionsOK) WithPayload(payload *models.ResponseDatabaseExtensions) *GetDatabaseExtensionsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get database extensions o k response +func (o *GetDatabaseExtensionsOK) SetPayload(payload *models.ResponseDatabaseExtensions) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDatabaseExtensionsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetDatabaseExtensionsBadRequestCode is the HTTP code returned for type GetDatabaseExtensionsBadRequest +const GetDatabaseExtensionsBadRequestCode int = 400 + +/* +GetDatabaseExtensionsBadRequest Error + +swagger:response getDatabaseExtensionsBadRequest +*/ +type GetDatabaseExtensionsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetDatabaseExtensionsBadRequest creates GetDatabaseExtensionsBadRequest with default headers values +func NewGetDatabaseExtensionsBadRequest() *GetDatabaseExtensionsBadRequest { + + return &GetDatabaseExtensionsBadRequest{} +} + +// WithPayload adds the payload to the get database extensions bad request response +func (o *GetDatabaseExtensionsBadRequest) WithPayload(payload *models.ResponseError) *GetDatabaseExtensionsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get database extensions bad request response +func (o *GetDatabaseExtensionsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDatabaseExtensionsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/dictionary/get_database_extensions_urlbuilder.go b/console/service/restapi/operations/dictionary/get_database_extensions_urlbuilder.go new file mode 100644 index 000000000..be2df9480 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_database_extensions_urlbuilder.go @@ -0,0 +1,132 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetDatabaseExtensionsURL generates an URL for the get database extensions operation +type GetDatabaseExtensionsURL struct { + ExtensionType *string + Limit *int64 + Offset *int64 + PostgresVersion *string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetDatabaseExtensionsURL) WithBasePath(bp string) *GetDatabaseExtensionsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetDatabaseExtensionsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetDatabaseExtensionsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/database/extensions" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var extensionTypeQ string + if o.ExtensionType != nil { + extensionTypeQ = *o.ExtensionType + } + if extensionTypeQ != "" { + qs.Set("extension_type", extensionTypeQ) + } + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + var postgresVersionQ string + if o.PostgresVersion != nil { + postgresVersionQ = *o.PostgresVersion + } + if postgresVersionQ != "" { + qs.Set("postgres_version", postgresVersionQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetDatabaseExtensionsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetDatabaseExtensionsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetDatabaseExtensionsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetDatabaseExtensionsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetDatabaseExtensionsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetDatabaseExtensionsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/dictionary/get_external_deployments.go b/console/service/restapi/operations/dictionary/get_external_deployments.go new file mode 100644 index 000000000..34a4652a7 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_external_deployments.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetExternalDeploymentsHandlerFunc turns a function with the right signature into a get external deployments handler +type GetExternalDeploymentsHandlerFunc func(GetExternalDeploymentsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetExternalDeploymentsHandlerFunc) Handle(params GetExternalDeploymentsParams) middleware.Responder { + return fn(params) +} + +// GetExternalDeploymentsHandler interface for that can handle valid get external deployments params +type GetExternalDeploymentsHandler interface { + Handle(GetExternalDeploymentsParams) middleware.Responder +} + +// NewGetExternalDeployments creates a new http.Handler for the get external deployments operation +func NewGetExternalDeployments(ctx *middleware.Context, handler GetExternalDeploymentsHandler) *GetExternalDeployments { + return &GetExternalDeployments{Context: ctx, Handler: handler} +} + +/* + GetExternalDeployments swagger:route GET /external/deployments dictionary getExternalDeployments + +Get full info about available external deployments +*/ +type GetExternalDeployments struct { + Context *middleware.Context + Handler GetExternalDeploymentsHandler +} + +func (o *GetExternalDeployments) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetExternalDeploymentsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/dictionary/get_external_deployments_parameters.go b/console/service/restapi/operations/dictionary/get_external_deployments_parameters.go new file mode 100644 index 000000000..f4ab09453 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_external_deployments_parameters.go @@ -0,0 +1,115 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetExternalDeploymentsParams creates a new GetExternalDeploymentsParams object +// +// There are no default values defined in the spec. +func NewGetExternalDeploymentsParams() GetExternalDeploymentsParams { + + return GetExternalDeploymentsParams{} +} + +// GetExternalDeploymentsParams contains all the bound params for the get external deployments operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetExternalDeployments +type GetExternalDeploymentsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + Limit *int64 + /* + In: query + */ + Offset *int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetExternalDeploymentsParams() beforehand. +func (o *GetExternalDeploymentsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetExternalDeploymentsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetExternalDeploymentsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} diff --git a/console/service/restapi/operations/dictionary/get_external_deployments_responses.go b/console/service/restapi/operations/dictionary/get_external_deployments_responses.go new file mode 100644 index 000000000..8e9a5d7c7 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_external_deployments_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetExternalDeploymentsOKCode is the HTTP code returned for type GetExternalDeploymentsOK +const GetExternalDeploymentsOKCode int = 200 + +/* +GetExternalDeploymentsOK OK + +swagger:response getExternalDeploymentsOK +*/ +type GetExternalDeploymentsOK struct { + + /* + In: Body + */ + Payload *models.ResponseDeploymentsInfo `json:"body,omitempty"` +} + +// NewGetExternalDeploymentsOK creates GetExternalDeploymentsOK with default headers values +func NewGetExternalDeploymentsOK() *GetExternalDeploymentsOK { + + return &GetExternalDeploymentsOK{} +} + +// WithPayload adds the payload to the get external deployments o k response +func (o *GetExternalDeploymentsOK) WithPayload(payload *models.ResponseDeploymentsInfo) *GetExternalDeploymentsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get external deployments o k response +func (o *GetExternalDeploymentsOK) SetPayload(payload *models.ResponseDeploymentsInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetExternalDeploymentsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetExternalDeploymentsBadRequestCode is the HTTP code returned for type GetExternalDeploymentsBadRequest +const GetExternalDeploymentsBadRequestCode int = 400 + +/* +GetExternalDeploymentsBadRequest Error + +swagger:response getExternalDeploymentsBadRequest +*/ +type GetExternalDeploymentsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetExternalDeploymentsBadRequest creates GetExternalDeploymentsBadRequest with default headers values +func NewGetExternalDeploymentsBadRequest() *GetExternalDeploymentsBadRequest { + + return &GetExternalDeploymentsBadRequest{} +} + +// WithPayload adds the payload to the get external deployments bad request response +func (o *GetExternalDeploymentsBadRequest) WithPayload(payload *models.ResponseError) *GetExternalDeploymentsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get external deployments bad request response +func (o *GetExternalDeploymentsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetExternalDeploymentsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/dictionary/get_external_deployments_urlbuilder.go b/console/service/restapi/operations/dictionary/get_external_deployments_urlbuilder.go new file mode 100644 index 000000000..73847e2a6 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_external_deployments_urlbuilder.go @@ -0,0 +1,114 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetExternalDeploymentsURL generates an URL for the get external deployments operation +type GetExternalDeploymentsURL struct { + Limit *int64 + Offset *int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetExternalDeploymentsURL) WithBasePath(bp string) *GetExternalDeploymentsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetExternalDeploymentsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetExternalDeploymentsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/external/deployments" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetExternalDeploymentsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetExternalDeploymentsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetExternalDeploymentsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetExternalDeploymentsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetExternalDeploymentsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetExternalDeploymentsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/dictionary/get_postgres_versions.go b/console/service/restapi/operations/dictionary/get_postgres_versions.go new file mode 100644 index 000000000..e6ac32966 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_postgres_versions.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetPostgresVersionsHandlerFunc turns a function with the right signature into a get postgres versions handler +type GetPostgresVersionsHandlerFunc func(GetPostgresVersionsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetPostgresVersionsHandlerFunc) Handle(params GetPostgresVersionsParams) middleware.Responder { + return fn(params) +} + +// GetPostgresVersionsHandler interface for that can handle valid get postgres versions params +type GetPostgresVersionsHandler interface { + Handle(GetPostgresVersionsParams) middleware.Responder +} + +// NewGetPostgresVersions creates a new http.Handler for the get postgres versions operation +func NewGetPostgresVersions(ctx *middleware.Context, handler GetPostgresVersionsHandler) *GetPostgresVersions { + return &GetPostgresVersions{Context: ctx, Handler: handler} +} + +/* + GetPostgresVersions swagger:route GET /postgres_versions dictionary getPostgresVersions + +Get supported postgres versions +*/ +type GetPostgresVersions struct { + Context *middleware.Context + Handler GetPostgresVersionsHandler +} + +func (o *GetPostgresVersions) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetPostgresVersionsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/dictionary/get_postgres_versions_parameters.go b/console/service/restapi/operations/dictionary/get_postgres_versions_parameters.go new file mode 100644 index 000000000..8d4670728 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_postgres_versions_parameters.go @@ -0,0 +1,46 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" +) + +// NewGetPostgresVersionsParams creates a new GetPostgresVersionsParams object +// +// There are no default values defined in the spec. +func NewGetPostgresVersionsParams() GetPostgresVersionsParams { + + return GetPostgresVersionsParams{} +} + +// GetPostgresVersionsParams contains all the bound params for the get postgres versions operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetPostgresVersions +type GetPostgresVersionsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetPostgresVersionsParams() beforehand. +func (o *GetPostgresVersionsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/dictionary/get_postgres_versions_responses.go b/console/service/restapi/operations/dictionary/get_postgres_versions_responses.go new file mode 100644 index 000000000..b6643e1a1 --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_postgres_versions_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetPostgresVersionsOKCode is the HTTP code returned for type GetPostgresVersionsOK +const GetPostgresVersionsOKCode int = 200 + +/* +GetPostgresVersionsOK OK + +swagger:response getPostgresVersionsOK +*/ +type GetPostgresVersionsOK struct { + + /* + In: Body + */ + Payload *models.ResponsePostgresVersions `json:"body,omitempty"` +} + +// NewGetPostgresVersionsOK creates GetPostgresVersionsOK with default headers values +func NewGetPostgresVersionsOK() *GetPostgresVersionsOK { + + return &GetPostgresVersionsOK{} +} + +// WithPayload adds the payload to the get postgres versions o k response +func (o *GetPostgresVersionsOK) WithPayload(payload *models.ResponsePostgresVersions) *GetPostgresVersionsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get postgres versions o k response +func (o *GetPostgresVersionsOK) SetPayload(payload *models.ResponsePostgresVersions) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetPostgresVersionsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetPostgresVersionsBadRequestCode is the HTTP code returned for type GetPostgresVersionsBadRequest +const GetPostgresVersionsBadRequestCode int = 400 + +/* +GetPostgresVersionsBadRequest Error + +swagger:response getPostgresVersionsBadRequest +*/ +type GetPostgresVersionsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetPostgresVersionsBadRequest creates GetPostgresVersionsBadRequest with default headers values +func NewGetPostgresVersionsBadRequest() *GetPostgresVersionsBadRequest { + + return &GetPostgresVersionsBadRequest{} +} + +// WithPayload adds the payload to the get postgres versions bad request response +func (o *GetPostgresVersionsBadRequest) WithPayload(payload *models.ResponseError) *GetPostgresVersionsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get postgres versions bad request response +func (o *GetPostgresVersionsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetPostgresVersionsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/dictionary/get_postgres_versions_urlbuilder.go b/console/service/restapi/operations/dictionary/get_postgres_versions_urlbuilder.go new file mode 100644 index 000000000..c54b42d6d --- /dev/null +++ b/console/service/restapi/operations/dictionary/get_postgres_versions_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package dictionary + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// GetPostgresVersionsURL generates an URL for the get postgres versions operation +type GetPostgresVersionsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetPostgresVersionsURL) WithBasePath(bp string) *GetPostgresVersionsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetPostgresVersionsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetPostgresVersionsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/postgres_versions" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetPostgresVersionsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetPostgresVersionsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetPostgresVersionsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetPostgresVersionsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetPostgresVersionsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetPostgresVersionsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/environment/delete_environments_id.go b/console/service/restapi/operations/environment/delete_environments_id.go new file mode 100644 index 000000000..3b9b72db3 --- /dev/null +++ b/console/service/restapi/operations/environment/delete_environments_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// DeleteEnvironmentsIDHandlerFunc turns a function with the right signature into a delete environments ID handler +type DeleteEnvironmentsIDHandlerFunc func(DeleteEnvironmentsIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteEnvironmentsIDHandlerFunc) Handle(params DeleteEnvironmentsIDParams) middleware.Responder { + return fn(params) +} + +// DeleteEnvironmentsIDHandler interface for that can handle valid delete environments ID params +type DeleteEnvironmentsIDHandler interface { + Handle(DeleteEnvironmentsIDParams) middleware.Responder +} + +// NewDeleteEnvironmentsID creates a new http.Handler for the delete environments ID operation +func NewDeleteEnvironmentsID(ctx *middleware.Context, handler DeleteEnvironmentsIDHandler) *DeleteEnvironmentsID { + return &DeleteEnvironmentsID{Context: ctx, Handler: handler} +} + +/* + DeleteEnvironmentsID swagger:route DELETE /environments/{id} environment deleteEnvironmentsId + +Delete environment +*/ +type DeleteEnvironmentsID struct { + Context *middleware.Context + Handler DeleteEnvironmentsIDHandler +} + +func (o *DeleteEnvironmentsID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewDeleteEnvironmentsIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/environment/delete_environments_id_parameters.go b/console/service/restapi/operations/environment/delete_environments_id_parameters.go new file mode 100644 index 000000000..24c502419 --- /dev/null +++ b/console/service/restapi/operations/environment/delete_environments_id_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewDeleteEnvironmentsIDParams creates a new DeleteEnvironmentsIDParams object +// +// There are no default values defined in the spec. +func NewDeleteEnvironmentsIDParams() DeleteEnvironmentsIDParams { + + return DeleteEnvironmentsIDParams{} +} + +// DeleteEnvironmentsIDParams contains all the bound params for the delete environments ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters DeleteEnvironmentsID +type DeleteEnvironmentsIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewDeleteEnvironmentsIDParams() beforehand. +func (o *DeleteEnvironmentsIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *DeleteEnvironmentsIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/environment/delete_environments_id_responses.go b/console/service/restapi/operations/environment/delete_environments_id_responses.go new file mode 100644 index 000000000..85f33ffa8 --- /dev/null +++ b/console/service/restapi/operations/environment/delete_environments_id_responses.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// DeleteEnvironmentsIDNoContentCode is the HTTP code returned for type DeleteEnvironmentsIDNoContent +const DeleteEnvironmentsIDNoContentCode int = 204 + +/* +DeleteEnvironmentsIDNoContent OK + +swagger:response deleteEnvironmentsIdNoContent +*/ +type DeleteEnvironmentsIDNoContent struct { +} + +// NewDeleteEnvironmentsIDNoContent creates DeleteEnvironmentsIDNoContent with default headers values +func NewDeleteEnvironmentsIDNoContent() *DeleteEnvironmentsIDNoContent { + + return &DeleteEnvironmentsIDNoContent{} +} + +// WriteResponse to the client +func (o *DeleteEnvironmentsIDNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +// DeleteEnvironmentsIDBadRequestCode is the HTTP code returned for type DeleteEnvironmentsIDBadRequest +const DeleteEnvironmentsIDBadRequestCode int = 400 + +/* +DeleteEnvironmentsIDBadRequest Error + +swagger:response deleteEnvironmentsIdBadRequest +*/ +type DeleteEnvironmentsIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewDeleteEnvironmentsIDBadRequest creates DeleteEnvironmentsIDBadRequest with default headers values +func NewDeleteEnvironmentsIDBadRequest() *DeleteEnvironmentsIDBadRequest { + + return &DeleteEnvironmentsIDBadRequest{} +} + +// WithPayload adds the payload to the delete environments Id bad request response +func (o *DeleteEnvironmentsIDBadRequest) WithPayload(payload *models.ResponseError) *DeleteEnvironmentsIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the delete environments Id bad request response +func (o *DeleteEnvironmentsIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *DeleteEnvironmentsIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/environment/delete_environments_id_urlbuilder.go b/console/service/restapi/operations/environment/delete_environments_id_urlbuilder.go new file mode 100644 index 000000000..1c70b9ee1 --- /dev/null +++ b/console/service/restapi/operations/environment/delete_environments_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// DeleteEnvironmentsIDURL generates an URL for the delete environments ID operation +type DeleteEnvironmentsIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteEnvironmentsIDURL) WithBasePath(bp string) *DeleteEnvironmentsIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteEnvironmentsIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteEnvironmentsIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/environments/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on DeleteEnvironmentsIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *DeleteEnvironmentsIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *DeleteEnvironmentsIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteEnvironmentsIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteEnvironmentsIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteEnvironmentsIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *DeleteEnvironmentsIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/environment/get_environments.go b/console/service/restapi/operations/environment/get_environments.go new file mode 100644 index 000000000..88d1e8c10 --- /dev/null +++ b/console/service/restapi/operations/environment/get_environments.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetEnvironmentsHandlerFunc turns a function with the right signature into a get environments handler +type GetEnvironmentsHandlerFunc func(GetEnvironmentsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetEnvironmentsHandlerFunc) Handle(params GetEnvironmentsParams) middleware.Responder { + return fn(params) +} + +// GetEnvironmentsHandler interface for that can handle valid get environments params +type GetEnvironmentsHandler interface { + Handle(GetEnvironmentsParams) middleware.Responder +} + +// NewGetEnvironments creates a new http.Handler for the get environments operation +func NewGetEnvironments(ctx *middleware.Context, handler GetEnvironmentsHandler) *GetEnvironments { + return &GetEnvironments{Context: ctx, Handler: handler} +} + +/* + GetEnvironments swagger:route GET /environments environment getEnvironments + +Get environemtns list +*/ +type GetEnvironments struct { + Context *middleware.Context + Handler GetEnvironmentsHandler +} + +func (o *GetEnvironments) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetEnvironmentsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/environment/get_environments_parameters.go b/console/service/restapi/operations/environment/get_environments_parameters.go new file mode 100644 index 000000000..90c25aae9 --- /dev/null +++ b/console/service/restapi/operations/environment/get_environments_parameters.go @@ -0,0 +1,115 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetEnvironmentsParams creates a new GetEnvironmentsParams object +// +// There are no default values defined in the spec. +func NewGetEnvironmentsParams() GetEnvironmentsParams { + + return GetEnvironmentsParams{} +} + +// GetEnvironmentsParams contains all the bound params for the get environments operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetEnvironments +type GetEnvironmentsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + Limit *int64 + /* + In: query + */ + Offset *int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetEnvironmentsParams() beforehand. +func (o *GetEnvironmentsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetEnvironmentsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetEnvironmentsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} diff --git a/console/service/restapi/operations/environment/get_environments_responses.go b/console/service/restapi/operations/environment/get_environments_responses.go new file mode 100644 index 000000000..10eeb4aef --- /dev/null +++ b/console/service/restapi/operations/environment/get_environments_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetEnvironmentsOKCode is the HTTP code returned for type GetEnvironmentsOK +const GetEnvironmentsOKCode int = 200 + +/* +GetEnvironmentsOK OK + +swagger:response getEnvironmentsOK +*/ +type GetEnvironmentsOK struct { + + /* + In: Body + */ + Payload *models.ResponseEnvironmentsList `json:"body,omitempty"` +} + +// NewGetEnvironmentsOK creates GetEnvironmentsOK with default headers values +func NewGetEnvironmentsOK() *GetEnvironmentsOK { + + return &GetEnvironmentsOK{} +} + +// WithPayload adds the payload to the get environments o k response +func (o *GetEnvironmentsOK) WithPayload(payload *models.ResponseEnvironmentsList) *GetEnvironmentsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get environments o k response +func (o *GetEnvironmentsOK) SetPayload(payload *models.ResponseEnvironmentsList) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetEnvironmentsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetEnvironmentsBadRequestCode is the HTTP code returned for type GetEnvironmentsBadRequest +const GetEnvironmentsBadRequestCode int = 400 + +/* +GetEnvironmentsBadRequest Error + +swagger:response getEnvironmentsBadRequest +*/ +type GetEnvironmentsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetEnvironmentsBadRequest creates GetEnvironmentsBadRequest with default headers values +func NewGetEnvironmentsBadRequest() *GetEnvironmentsBadRequest { + + return &GetEnvironmentsBadRequest{} +} + +// WithPayload adds the payload to the get environments bad request response +func (o *GetEnvironmentsBadRequest) WithPayload(payload *models.ResponseError) *GetEnvironmentsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get environments bad request response +func (o *GetEnvironmentsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetEnvironmentsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/environment/get_environments_urlbuilder.go b/console/service/restapi/operations/environment/get_environments_urlbuilder.go new file mode 100644 index 000000000..91cd1362b --- /dev/null +++ b/console/service/restapi/operations/environment/get_environments_urlbuilder.go @@ -0,0 +1,114 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetEnvironmentsURL generates an URL for the get environments operation +type GetEnvironmentsURL struct { + Limit *int64 + Offset *int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetEnvironmentsURL) WithBasePath(bp string) *GetEnvironmentsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetEnvironmentsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetEnvironmentsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/environments" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetEnvironmentsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetEnvironmentsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetEnvironmentsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetEnvironmentsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetEnvironmentsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetEnvironmentsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/environment/post_environments.go b/console/service/restapi/operations/environment/post_environments.go new file mode 100644 index 000000000..6b17684a4 --- /dev/null +++ b/console/service/restapi/operations/environment/post_environments.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostEnvironmentsHandlerFunc turns a function with the right signature into a post environments handler +type PostEnvironmentsHandlerFunc func(PostEnvironmentsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostEnvironmentsHandlerFunc) Handle(params PostEnvironmentsParams) middleware.Responder { + return fn(params) +} + +// PostEnvironmentsHandler interface for that can handle valid post environments params +type PostEnvironmentsHandler interface { + Handle(PostEnvironmentsParams) middleware.Responder +} + +// NewPostEnvironments creates a new http.Handler for the post environments operation +func NewPostEnvironments(ctx *middleware.Context, handler PostEnvironmentsHandler) *PostEnvironments { + return &PostEnvironments{Context: ctx, Handler: handler} +} + +/* + PostEnvironments swagger:route POST /environments environment postEnvironments + +Create environemtn +*/ +type PostEnvironments struct { + Context *middleware.Context + Handler PostEnvironmentsHandler +} + +func (o *PostEnvironments) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostEnvironmentsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/environment/post_environments_parameters.go b/console/service/restapi/operations/environment/post_environments_parameters.go new file mode 100644 index 000000000..e3336fa01 --- /dev/null +++ b/console/service/restapi/operations/environment/post_environments_parameters.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPostEnvironmentsParams creates a new PostEnvironmentsParams object +// +// There are no default values defined in the spec. +func NewPostEnvironmentsParams() PostEnvironmentsParams { + + return PostEnvironmentsParams{} +} + +// PostEnvironmentsParams contains all the bound params for the post environments operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostEnvironments +type PostEnvironmentsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestEnvironment +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostEnvironmentsParams() beforehand. +func (o *PostEnvironmentsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestEnvironment + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/environment/post_environments_responses.go b/console/service/restapi/operations/environment/post_environments_responses.go new file mode 100644 index 000000000..87f7e0779 --- /dev/null +++ b/console/service/restapi/operations/environment/post_environments_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostEnvironmentsOKCode is the HTTP code returned for type PostEnvironmentsOK +const PostEnvironmentsOKCode int = 200 + +/* +PostEnvironmentsOK OK + +swagger:response postEnvironmentsOK +*/ +type PostEnvironmentsOK struct { + + /* + In: Body + */ + Payload *models.ResponseEnvironment `json:"body,omitempty"` +} + +// NewPostEnvironmentsOK creates PostEnvironmentsOK with default headers values +func NewPostEnvironmentsOK() *PostEnvironmentsOK { + + return &PostEnvironmentsOK{} +} + +// WithPayload adds the payload to the post environments o k response +func (o *PostEnvironmentsOK) WithPayload(payload *models.ResponseEnvironment) *PostEnvironmentsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post environments o k response +func (o *PostEnvironmentsOK) SetPayload(payload *models.ResponseEnvironment) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostEnvironmentsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostEnvironmentsBadRequestCode is the HTTP code returned for type PostEnvironmentsBadRequest +const PostEnvironmentsBadRequestCode int = 400 + +/* +PostEnvironmentsBadRequest Error + +swagger:response postEnvironmentsBadRequest +*/ +type PostEnvironmentsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostEnvironmentsBadRequest creates PostEnvironmentsBadRequest with default headers values +func NewPostEnvironmentsBadRequest() *PostEnvironmentsBadRequest { + + return &PostEnvironmentsBadRequest{} +} + +// WithPayload adds the payload to the post environments bad request response +func (o *PostEnvironmentsBadRequest) WithPayload(payload *models.ResponseError) *PostEnvironmentsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post environments bad request response +func (o *PostEnvironmentsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostEnvironmentsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/environment/post_environments_urlbuilder.go b/console/service/restapi/operations/environment/post_environments_urlbuilder.go new file mode 100644 index 000000000..879977065 --- /dev/null +++ b/console/service/restapi/operations/environment/post_environments_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package environment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// PostEnvironmentsURL generates an URL for the post environments operation +type PostEnvironmentsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostEnvironmentsURL) WithBasePath(bp string) *PostEnvironmentsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostEnvironmentsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostEnvironmentsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/environments" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostEnvironmentsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostEnvironmentsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostEnvironmentsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostEnvironmentsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostEnvironmentsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostEnvironmentsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/operation/get_operations.go b/console/service/restapi/operations/operation/get_operations.go new file mode 100644 index 000000000..0e61544c1 --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetOperationsHandlerFunc turns a function with the right signature into a get operations handler +type GetOperationsHandlerFunc func(GetOperationsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetOperationsHandlerFunc) Handle(params GetOperationsParams) middleware.Responder { + return fn(params) +} + +// GetOperationsHandler interface for that can handle valid get operations params +type GetOperationsHandler interface { + Handle(GetOperationsParams) middleware.Responder +} + +// NewGetOperations creates a new http.Handler for the get operations operation +func NewGetOperations(ctx *middleware.Context, handler GetOperationsHandler) *GetOperations { + return &GetOperations{Context: ctx, Handler: handler} +} + +/* + GetOperations swagger:route GET /operations operation getOperations + +Get operations list for current project +*/ +type GetOperations struct { + Context *middleware.Context + Handler GetOperationsHandler +} + +func (o *GetOperations) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetOperationsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/operation/get_operations_id_log.go b/console/service/restapi/operations/operation/get_operations_id_log.go new file mode 100644 index 000000000..74e6007ef --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations_id_log.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetOperationsIDLogHandlerFunc turns a function with the right signature into a get operations ID log handler +type GetOperationsIDLogHandlerFunc func(GetOperationsIDLogParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetOperationsIDLogHandlerFunc) Handle(params GetOperationsIDLogParams) middleware.Responder { + return fn(params) +} + +// GetOperationsIDLogHandler interface for that can handle valid get operations ID log params +type GetOperationsIDLogHandler interface { + Handle(GetOperationsIDLogParams) middleware.Responder +} + +// NewGetOperationsIDLog creates a new http.Handler for the get operations ID log operation +func NewGetOperationsIDLog(ctx *middleware.Context, handler GetOperationsIDLogHandler) *GetOperationsIDLog { + return &GetOperationsIDLog{Context: ctx, Handler: handler} +} + +/* + GetOperationsIDLog swagger:route GET /operations/{id}/log operation getOperationsIdLog + +Get operation log by operation_id +*/ +type GetOperationsIDLog struct { + Context *middleware.Context + Handler GetOperationsIDLogHandler +} + +func (o *GetOperationsIDLog) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetOperationsIDLogParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/operation/get_operations_id_log_parameters.go b/console/service/restapi/operations/operation/get_operations_id_log_parameters.go new file mode 100644 index 000000000..a7c718948 --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations_id_log_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetOperationsIDLogParams creates a new GetOperationsIDLogParams object +// +// There are no default values defined in the spec. +func NewGetOperationsIDLogParams() GetOperationsIDLogParams { + + return GetOperationsIDLogParams{} +} + +// GetOperationsIDLogParams contains all the bound params for the get operations ID log operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetOperationsIDLog +type GetOperationsIDLogParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Operation id + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetOperationsIDLogParams() beforehand. +func (o *GetOperationsIDLogParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *GetOperationsIDLogParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/operation/get_operations_id_log_responses.go b/console/service/restapi/operations/operation/get_operations_id_log_responses.go new file mode 100644 index 000000000..cdc816538 --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations_id_log_responses.go @@ -0,0 +1,147 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/models" +) + +// GetOperationsIDLogOKCode is the HTTP code returned for type GetOperationsIDLogOK +const GetOperationsIDLogOKCode int = 200 + +/* +GetOperationsIDLogOK OK + +swagger:response getOperationsIdLogOK +*/ +type GetOperationsIDLogOK struct { + /* + + */ + ContentType string `json:"content-type"` + /* + + */ + XLogCompleted bool `json:"x-log-completed"` + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetOperationsIDLogOK creates GetOperationsIDLogOK with default headers values +func NewGetOperationsIDLogOK() *GetOperationsIDLogOK { + + return &GetOperationsIDLogOK{} +} + +// WithContentType adds the contentType to the get operations Id log o k response +func (o *GetOperationsIDLogOK) WithContentType(contentType string) *GetOperationsIDLogOK { + o.ContentType = contentType + return o +} + +// SetContentType sets the contentType to the get operations Id log o k response +func (o *GetOperationsIDLogOK) SetContentType(contentType string) { + o.ContentType = contentType +} + +// WithXLogCompleted adds the xLogCompleted to the get operations Id log o k response +func (o *GetOperationsIDLogOK) WithXLogCompleted(xLogCompleted bool) *GetOperationsIDLogOK { + o.XLogCompleted = xLogCompleted + return o +} + +// SetXLogCompleted sets the xLogCompleted to the get operations Id log o k response +func (o *GetOperationsIDLogOK) SetXLogCompleted(xLogCompleted bool) { + o.XLogCompleted = xLogCompleted +} + +// WithPayload adds the payload to the get operations Id log o k response +func (o *GetOperationsIDLogOK) WithPayload(payload string) *GetOperationsIDLogOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get operations Id log o k response +func (o *GetOperationsIDLogOK) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetOperationsIDLogOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // response header content-type + + contentType := o.ContentType + if contentType != "" { + rw.Header().Set("content-type", contentType) + } + + // response header x-log-completed + + xLogCompleted := swag.FormatBool(o.XLogCompleted) + if xLogCompleted != "" { + rw.Header().Set("x-log-completed", xLogCompleted) + } + + rw.WriteHeader(200) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetOperationsIDLogBadRequestCode is the HTTP code returned for type GetOperationsIDLogBadRequest +const GetOperationsIDLogBadRequestCode int = 400 + +/* +GetOperationsIDLogBadRequest Error + +swagger:response getOperationsIdLogBadRequest +*/ +type GetOperationsIDLogBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetOperationsIDLogBadRequest creates GetOperationsIDLogBadRequest with default headers values +func NewGetOperationsIDLogBadRequest() *GetOperationsIDLogBadRequest { + + return &GetOperationsIDLogBadRequest{} +} + +// WithPayload adds the payload to the get operations Id log bad request response +func (o *GetOperationsIDLogBadRequest) WithPayload(payload *models.ResponseError) *GetOperationsIDLogBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get operations Id log bad request response +func (o *GetOperationsIDLogBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetOperationsIDLogBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/operation/get_operations_id_log_urlbuilder.go b/console/service/restapi/operations/operation/get_operations_id_log_urlbuilder.go new file mode 100644 index 000000000..308133be8 --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations_id_log_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// GetOperationsIDLogURL generates an URL for the get operations ID log operation +type GetOperationsIDLogURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetOperationsIDLogURL) WithBasePath(bp string) *GetOperationsIDLogURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetOperationsIDLogURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetOperationsIDLogURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/operations/{id}/log" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on GetOperationsIDLogURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetOperationsIDLogURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetOperationsIDLogURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetOperationsIDLogURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetOperationsIDLogURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetOperationsIDLogURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetOperationsIDLogURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/operation/get_operations_parameters.go b/console/service/restapi/operations/operation/get_operations_parameters.go new file mode 100644 index 000000000..3aeccb99f --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations_parameters.go @@ -0,0 +1,397 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewGetOperationsParams creates a new GetOperationsParams object +// +// There are no default values defined in the spec. +func NewGetOperationsParams() GetOperationsParams { + + return GetOperationsParams{} +} + +// GetOperationsParams contains all the bound params for the get operations operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetOperations +type GetOperationsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Filter by cluster_name + In: query + */ + ClusterName *string + /*Operations started till this date + Required: true + In: query + */ + EndDate strfmt.DateTime + /*Filter by environment + In: query + */ + Environment *string + /* + In: query + */ + Limit *int64 + /* + In: query + */ + Offset *int64 + /*Required parameter for filter + Required: true + In: query + */ + ProjectID int64 + /*Sort by fields. Example: sort_by=cluster_name,-type,status,id,created_at,updated_at + Supported valuese: + - id + - cluster_name + - type + - status + - started_at + - updated_at + - cluster + - environment + + In: query + */ + SortBy *string + /*Operations started after this date + Required: true + In: query + */ + StartDate strfmt.DateTime + /*Filter by status + In: query + */ + Status *string + /*Filter by type + In: query + */ + Type *string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetOperationsParams() beforehand. +func (o *GetOperationsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qClusterName, qhkClusterName, _ := qs.GetOK("cluster_name") + if err := o.bindClusterName(qClusterName, qhkClusterName, route.Formats); err != nil { + res = append(res, err) + } + + qEndDate, qhkEndDate, _ := qs.GetOK("end_date") + if err := o.bindEndDate(qEndDate, qhkEndDate, route.Formats); err != nil { + res = append(res, err) + } + + qEnvironment, qhkEnvironment, _ := qs.GetOK("environment") + if err := o.bindEnvironment(qEnvironment, qhkEnvironment, route.Formats); err != nil { + res = append(res, err) + } + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + + qProjectID, qhkProjectID, _ := qs.GetOK("project_id") + if err := o.bindProjectID(qProjectID, qhkProjectID, route.Formats); err != nil { + res = append(res, err) + } + + qSortBy, qhkSortBy, _ := qs.GetOK("sort_by") + if err := o.bindSortBy(qSortBy, qhkSortBy, route.Formats); err != nil { + res = append(res, err) + } + + qStartDate, qhkStartDate, _ := qs.GetOK("start_date") + if err := o.bindStartDate(qStartDate, qhkStartDate, route.Formats); err != nil { + res = append(res, err) + } + + qStatus, qhkStatus, _ := qs.GetOK("status") + if err := o.bindStatus(qStatus, qhkStatus, route.Formats); err != nil { + res = append(res, err) + } + + qType, qhkType, _ := qs.GetOK("type") + if err := o.bindType(qType, qhkType, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindClusterName binds and validates parameter ClusterName from query. +func (o *GetOperationsParams) bindClusterName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.ClusterName = &raw + + return nil +} + +// bindEndDate binds and validates parameter EndDate from query. +func (o *GetOperationsParams) bindEndDate(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("end_date", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("end_date", "query", raw); err != nil { + return err + } + + // Format: date-time + value, err := formats.Parse("date-time", raw) + if err != nil { + return errors.InvalidType("end_date", "query", "strfmt.DateTime", raw) + } + o.EndDate = *(value.(*strfmt.DateTime)) + + if err := o.validateEndDate(formats); err != nil { + return err + } + + return nil +} + +// validateEndDate carries on validations for parameter EndDate +func (o *GetOperationsParams) validateEndDate(formats strfmt.Registry) error { + + if err := validate.FormatOf("end_date", "query", "date-time", o.EndDate.String(), formats); err != nil { + return err + } + return nil +} + +// bindEnvironment binds and validates parameter Environment from query. +func (o *GetOperationsParams) bindEnvironment(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Environment = &raw + + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetOperationsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetOperationsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} + +// bindProjectID binds and validates parameter ProjectID from query. +func (o *GetOperationsParams) bindProjectID(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("project_id", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("project_id", "query", raw); err != nil { + return err + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("project_id", "query", "int64", raw) + } + o.ProjectID = value + + return nil +} + +// bindSortBy binds and validates parameter SortBy from query. +func (o *GetOperationsParams) bindSortBy(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.SortBy = &raw + + return nil +} + +// bindStartDate binds and validates parameter StartDate from query. +func (o *GetOperationsParams) bindStartDate(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("start_date", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("start_date", "query", raw); err != nil { + return err + } + + // Format: date-time + value, err := formats.Parse("date-time", raw) + if err != nil { + return errors.InvalidType("start_date", "query", "strfmt.DateTime", raw) + } + o.StartDate = *(value.(*strfmt.DateTime)) + + if err := o.validateStartDate(formats); err != nil { + return err + } + + return nil +} + +// validateStartDate carries on validations for parameter StartDate +func (o *GetOperationsParams) validateStartDate(formats strfmt.Registry) error { + + if err := validate.FormatOf("start_date", "query", "date-time", o.StartDate.String(), formats); err != nil { + return err + } + return nil +} + +// bindStatus binds and validates parameter Status from query. +func (o *GetOperationsParams) bindStatus(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Status = &raw + + return nil +} + +// bindType binds and validates parameter Type from query. +func (o *GetOperationsParams) bindType(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Type = &raw + + return nil +} diff --git a/console/service/restapi/operations/operation/get_operations_responses.go b/console/service/restapi/operations/operation/get_operations_responses.go new file mode 100644 index 000000000..40f8148a6 --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetOperationsOKCode is the HTTP code returned for type GetOperationsOK +const GetOperationsOKCode int = 200 + +/* +GetOperationsOK OK + +swagger:response getOperationsOK +*/ +type GetOperationsOK struct { + + /* + In: Body + */ + Payload *models.ResponseOperationsList `json:"body,omitempty"` +} + +// NewGetOperationsOK creates GetOperationsOK with default headers values +func NewGetOperationsOK() *GetOperationsOK { + + return &GetOperationsOK{} +} + +// WithPayload adds the payload to the get operations o k response +func (o *GetOperationsOK) WithPayload(payload *models.ResponseOperationsList) *GetOperationsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get operations o k response +func (o *GetOperationsOK) SetPayload(payload *models.ResponseOperationsList) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetOperationsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetOperationsBadRequestCode is the HTTP code returned for type GetOperationsBadRequest +const GetOperationsBadRequestCode int = 400 + +/* +GetOperationsBadRequest Error + +swagger:response getOperationsBadRequest +*/ +type GetOperationsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetOperationsBadRequest creates GetOperationsBadRequest with default headers values +func NewGetOperationsBadRequest() *GetOperationsBadRequest { + + return &GetOperationsBadRequest{} +} + +// WithPayload adds the payload to the get operations bad request response +func (o *GetOperationsBadRequest) WithPayload(payload *models.ResponseError) *GetOperationsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get operations bad request response +func (o *GetOperationsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetOperationsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/operation/get_operations_urlbuilder.go b/console/service/restapi/operations/operation/get_operations_urlbuilder.go new file mode 100644 index 000000000..354593fdc --- /dev/null +++ b/console/service/restapi/operations/operation/get_operations_urlbuilder.go @@ -0,0 +1,178 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operation + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// GetOperationsURL generates an URL for the get operations operation +type GetOperationsURL struct { + ClusterName *string + EndDate strfmt.DateTime + Environment *string + Limit *int64 + Offset *int64 + ProjectID int64 + SortBy *string + StartDate strfmt.DateTime + Status *string + Type *string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetOperationsURL) WithBasePath(bp string) *GetOperationsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetOperationsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetOperationsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/operations" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var clusterNameQ string + if o.ClusterName != nil { + clusterNameQ = *o.ClusterName + } + if clusterNameQ != "" { + qs.Set("cluster_name", clusterNameQ) + } + + endDateQ := o.EndDate.String() + if endDateQ != "" { + qs.Set("end_date", endDateQ) + } + + var environmentQ string + if o.Environment != nil { + environmentQ = *o.Environment + } + if environmentQ != "" { + qs.Set("environment", environmentQ) + } + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + projectIDQ := swag.FormatInt64(o.ProjectID) + if projectIDQ != "" { + qs.Set("project_id", projectIDQ) + } + + var sortByQ string + if o.SortBy != nil { + sortByQ = *o.SortBy + } + if sortByQ != "" { + qs.Set("sort_by", sortByQ) + } + + startDateQ := o.StartDate.String() + if startDateQ != "" { + qs.Set("start_date", startDateQ) + } + + var statusQ string + if o.Status != nil { + statusQ = *o.Status + } + if statusQ != "" { + qs.Set("status", statusQ) + } + + var typeVarQ string + if o.Type != nil { + typeVarQ = *o.Type + } + if typeVarQ != "" { + qs.Set("type", typeVarQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetOperationsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetOperationsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetOperationsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetOperationsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetOperationsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetOperationsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/pg_console_api.go b/console/service/restapi/operations/pg_console_api.go new file mode 100644 index 000000000..23cdcb086 --- /dev/null +++ b/console/service/restapi/operations/pg_console_api.go @@ -0,0 +1,706 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + "net/http" + "strings" + + "github.com/go-openapi/errors" + "github.com/go-openapi/loads" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/runtime/security" + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "postgesql-cluster-console/restapi/operations/cluster" + "postgesql-cluster-console/restapi/operations/dictionary" + "postgesql-cluster-console/restapi/operations/environment" + "postgesql-cluster-console/restapi/operations/operation" + "postgesql-cluster-console/restapi/operations/project" + "postgesql-cluster-console/restapi/operations/secret" + "postgesql-cluster-console/restapi/operations/setting" + "postgesql-cluster-console/restapi/operations/system" +) + +// NewPgConsoleAPI creates a new PgConsole instance +func NewPgConsoleAPI(spec *loads.Document) *PgConsoleAPI { + return &PgConsoleAPI{ + handlers: make(map[string]map[string]http.Handler), + formats: strfmt.Default, + defaultConsumes: "application/json", + defaultProduces: "application/json", + customConsumers: make(map[string]runtime.Consumer), + customProducers: make(map[string]runtime.Producer), + PreServerShutdown: func() {}, + ServerShutdown: func() {}, + spec: spec, + useSwaggerUI: false, + ServeError: errors.ServeError, + BasicAuthenticator: security.BasicAuth, + APIKeyAuthenticator: security.APIKeyAuth, + BearerAuthenticator: security.BearerAuth, + + JSONConsumer: runtime.JSONConsumer(), + PlainTextConsumer: runtime.ConsumerFunc(func(r io.Reader, target interface{}) error { + return errors.NotImplemented("plainText consumer has not yet been implemented") + }), + + JSONProducer: runtime.JSONProducer(), + + ClusterDeleteClustersIDHandler: cluster.DeleteClustersIDHandlerFunc(func(params cluster.DeleteClustersIDParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.DeleteClustersID has not yet been implemented") + }), + EnvironmentDeleteEnvironmentsIDHandler: environment.DeleteEnvironmentsIDHandlerFunc(func(params environment.DeleteEnvironmentsIDParams) middleware.Responder { + return middleware.NotImplemented("operation environment.DeleteEnvironmentsID has not yet been implemented") + }), + ProjectDeleteProjectsIDHandler: project.DeleteProjectsIDHandlerFunc(func(params project.DeleteProjectsIDParams) middleware.Responder { + return middleware.NotImplemented("operation project.DeleteProjectsID has not yet been implemented") + }), + SecretDeleteSecretsIDHandler: secret.DeleteSecretsIDHandlerFunc(func(params secret.DeleteSecretsIDParams) middleware.Responder { + return middleware.NotImplemented("operation secret.DeleteSecretsID has not yet been implemented") + }), + ClusterDeleteServersIDHandler: cluster.DeleteServersIDHandlerFunc(func(params cluster.DeleteServersIDParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.DeleteServersID has not yet been implemented") + }), + ClusterGetClustersHandler: cluster.GetClustersHandlerFunc(func(params cluster.GetClustersParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.GetClusters has not yet been implemented") + }), + ClusterGetClustersDefaultNameHandler: cluster.GetClustersDefaultNameHandlerFunc(func(params cluster.GetClustersDefaultNameParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.GetClustersDefaultName has not yet been implemented") + }), + ClusterGetClustersIDHandler: cluster.GetClustersIDHandlerFunc(func(params cluster.GetClustersIDParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.GetClustersID has not yet been implemented") + }), + DictionaryGetDatabaseExtensionsHandler: dictionary.GetDatabaseExtensionsHandlerFunc(func(params dictionary.GetDatabaseExtensionsParams) middleware.Responder { + return middleware.NotImplemented("operation dictionary.GetDatabaseExtensions has not yet been implemented") + }), + EnvironmentGetEnvironmentsHandler: environment.GetEnvironmentsHandlerFunc(func(params environment.GetEnvironmentsParams) middleware.Responder { + return middleware.NotImplemented("operation environment.GetEnvironments has not yet been implemented") + }), + DictionaryGetExternalDeploymentsHandler: dictionary.GetExternalDeploymentsHandlerFunc(func(params dictionary.GetExternalDeploymentsParams) middleware.Responder { + return middleware.NotImplemented("operation dictionary.GetExternalDeployments has not yet been implemented") + }), + OperationGetOperationsHandler: operation.GetOperationsHandlerFunc(func(params operation.GetOperationsParams) middleware.Responder { + return middleware.NotImplemented("operation operation.GetOperations has not yet been implemented") + }), + OperationGetOperationsIDLogHandler: operation.GetOperationsIDLogHandlerFunc(func(params operation.GetOperationsIDLogParams) middleware.Responder { + return middleware.NotImplemented("operation operation.GetOperationsIDLog has not yet been implemented") + }), + DictionaryGetPostgresVersionsHandler: dictionary.GetPostgresVersionsHandlerFunc(func(params dictionary.GetPostgresVersionsParams) middleware.Responder { + return middleware.NotImplemented("operation dictionary.GetPostgresVersions has not yet been implemented") + }), + ProjectGetProjectsHandler: project.GetProjectsHandlerFunc(func(params project.GetProjectsParams) middleware.Responder { + return middleware.NotImplemented("operation project.GetProjects has not yet been implemented") + }), + SecretGetSecretsHandler: secret.GetSecretsHandlerFunc(func(params secret.GetSecretsParams) middleware.Responder { + return middleware.NotImplemented("operation secret.GetSecrets has not yet been implemented") + }), + SettingGetSettingsHandler: setting.GetSettingsHandlerFunc(func(params setting.GetSettingsParams) middleware.Responder { + return middleware.NotImplemented("operation setting.GetSettings has not yet been implemented") + }), + SystemGetVersionHandler: system.GetVersionHandlerFunc(func(params system.GetVersionParams) middleware.Responder { + return middleware.NotImplemented("operation system.GetVersion has not yet been implemented") + }), + ProjectPatchProjectsIDHandler: project.PatchProjectsIDHandlerFunc(func(params project.PatchProjectsIDParams) middleware.Responder { + return middleware.NotImplemented("operation project.PatchProjectsID has not yet been implemented") + }), + SecretPatchSecretsIDHandler: secret.PatchSecretsIDHandlerFunc(func(params secret.PatchSecretsIDParams) middleware.Responder { + return middleware.NotImplemented("operation secret.PatchSecretsID has not yet been implemented") + }), + SettingPatchSettingsNameHandler: setting.PatchSettingsNameHandlerFunc(func(params setting.PatchSettingsNameParams) middleware.Responder { + return middleware.NotImplemented("operation setting.PatchSettingsName has not yet been implemented") + }), + ClusterPostClustersHandler: cluster.PostClustersHandlerFunc(func(params cluster.PostClustersParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClusters has not yet been implemented") + }), + ClusterPostClustersIDRefreshHandler: cluster.PostClustersIDRefreshHandlerFunc(func(params cluster.PostClustersIDRefreshParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDRefresh has not yet been implemented") + }), + ClusterPostClustersIDReinitHandler: cluster.PostClustersIDReinitHandlerFunc(func(params cluster.PostClustersIDReinitParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDReinit has not yet been implemented") + }), + ClusterPostClustersIDReloadHandler: cluster.PostClustersIDReloadHandlerFunc(func(params cluster.PostClustersIDReloadParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDReload has not yet been implemented") + }), + ClusterPostClustersIDRemoveHandler: cluster.PostClustersIDRemoveHandlerFunc(func(params cluster.PostClustersIDRemoveParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDRemove has not yet been implemented") + }), + ClusterPostClustersIDRestartHandler: cluster.PostClustersIDRestartHandlerFunc(func(params cluster.PostClustersIDRestartParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDRestart has not yet been implemented") + }), + ClusterPostClustersIDStartHandler: cluster.PostClustersIDStartHandlerFunc(func(params cluster.PostClustersIDStartParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDStart has not yet been implemented") + }), + ClusterPostClustersIDStopHandler: cluster.PostClustersIDStopHandlerFunc(func(params cluster.PostClustersIDStopParams) middleware.Responder { + return middleware.NotImplemented("operation cluster.PostClustersIDStop has not yet been implemented") + }), + EnvironmentPostEnvironmentsHandler: environment.PostEnvironmentsHandlerFunc(func(params environment.PostEnvironmentsParams) middleware.Responder { + return middleware.NotImplemented("operation environment.PostEnvironments has not yet been implemented") + }), + ProjectPostProjectsHandler: project.PostProjectsHandlerFunc(func(params project.PostProjectsParams) middleware.Responder { + return middleware.NotImplemented("operation project.PostProjects has not yet been implemented") + }), + SecretPostSecretsHandler: secret.PostSecretsHandlerFunc(func(params secret.PostSecretsParams) middleware.Responder { + return middleware.NotImplemented("operation secret.PostSecrets has not yet been implemented") + }), + SettingPostSettingsHandler: setting.PostSettingsHandlerFunc(func(params setting.PostSettingsParams) middleware.Responder { + return middleware.NotImplemented("operation setting.PostSettings has not yet been implemented") + }), + } +} + +/*PgConsoleAPI API for PG Console WEB */ +type PgConsoleAPI struct { + spec *loads.Document + context *middleware.Context + handlers map[string]map[string]http.Handler + formats strfmt.Registry + customConsumers map[string]runtime.Consumer + customProducers map[string]runtime.Producer + defaultConsumes string + defaultProduces string + Middleware func(middleware.Builder) http.Handler + useSwaggerUI bool + + // BasicAuthenticator generates a runtime.Authenticator from the supplied basic auth function. + // It has a default implementation in the security package, however you can replace it for your particular usage. + BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator + + // APIKeyAuthenticator generates a runtime.Authenticator from the supplied token auth function. + // It has a default implementation in the security package, however you can replace it for your particular usage. + APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator + + // BearerAuthenticator generates a runtime.Authenticator from the supplied bearer token auth function. + // It has a default implementation in the security package, however you can replace it for your particular usage. + BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator + + // JSONConsumer registers a consumer for the following mime types: + // - application/json + JSONConsumer runtime.Consumer + // PlainTextConsumer registers a consumer for the following mime types: + // - plain/text + PlainTextConsumer runtime.Consumer + + // JSONProducer registers a producer for the following mime types: + // - application/json + JSONProducer runtime.Producer + + // ClusterDeleteClustersIDHandler sets the operation handler for the delete clusters ID operation + ClusterDeleteClustersIDHandler cluster.DeleteClustersIDHandler + // EnvironmentDeleteEnvironmentsIDHandler sets the operation handler for the delete environments ID operation + EnvironmentDeleteEnvironmentsIDHandler environment.DeleteEnvironmentsIDHandler + // ProjectDeleteProjectsIDHandler sets the operation handler for the delete projects ID operation + ProjectDeleteProjectsIDHandler project.DeleteProjectsIDHandler + // SecretDeleteSecretsIDHandler sets the operation handler for the delete secrets ID operation + SecretDeleteSecretsIDHandler secret.DeleteSecretsIDHandler + // ClusterDeleteServersIDHandler sets the operation handler for the delete servers ID operation + ClusterDeleteServersIDHandler cluster.DeleteServersIDHandler + // ClusterGetClustersHandler sets the operation handler for the get clusters operation + ClusterGetClustersHandler cluster.GetClustersHandler + // ClusterGetClustersDefaultNameHandler sets the operation handler for the get clusters default name operation + ClusterGetClustersDefaultNameHandler cluster.GetClustersDefaultNameHandler + // ClusterGetClustersIDHandler sets the operation handler for the get clusters ID operation + ClusterGetClustersIDHandler cluster.GetClustersIDHandler + // DictionaryGetDatabaseExtensionsHandler sets the operation handler for the get database extensions operation + DictionaryGetDatabaseExtensionsHandler dictionary.GetDatabaseExtensionsHandler + // EnvironmentGetEnvironmentsHandler sets the operation handler for the get environments operation + EnvironmentGetEnvironmentsHandler environment.GetEnvironmentsHandler + // DictionaryGetExternalDeploymentsHandler sets the operation handler for the get external deployments operation + DictionaryGetExternalDeploymentsHandler dictionary.GetExternalDeploymentsHandler + // OperationGetOperationsHandler sets the operation handler for the get operations operation + OperationGetOperationsHandler operation.GetOperationsHandler + // OperationGetOperationsIDLogHandler sets the operation handler for the get operations ID log operation + OperationGetOperationsIDLogHandler operation.GetOperationsIDLogHandler + // DictionaryGetPostgresVersionsHandler sets the operation handler for the get postgres versions operation + DictionaryGetPostgresVersionsHandler dictionary.GetPostgresVersionsHandler + // ProjectGetProjectsHandler sets the operation handler for the get projects operation + ProjectGetProjectsHandler project.GetProjectsHandler + // SecretGetSecretsHandler sets the operation handler for the get secrets operation + SecretGetSecretsHandler secret.GetSecretsHandler + // SettingGetSettingsHandler sets the operation handler for the get settings operation + SettingGetSettingsHandler setting.GetSettingsHandler + // SystemGetVersionHandler sets the operation handler for the get version operation + SystemGetVersionHandler system.GetVersionHandler + // ProjectPatchProjectsIDHandler sets the operation handler for the patch projects ID operation + ProjectPatchProjectsIDHandler project.PatchProjectsIDHandler + // SecretPatchSecretsIDHandler sets the operation handler for the patch secrets ID operation + SecretPatchSecretsIDHandler secret.PatchSecretsIDHandler + // SettingPatchSettingsNameHandler sets the operation handler for the patch settings name operation + SettingPatchSettingsNameHandler setting.PatchSettingsNameHandler + // ClusterPostClustersHandler sets the operation handler for the post clusters operation + ClusterPostClustersHandler cluster.PostClustersHandler + // ClusterPostClustersIDRefreshHandler sets the operation handler for the post clusters ID refresh operation + ClusterPostClustersIDRefreshHandler cluster.PostClustersIDRefreshHandler + // ClusterPostClustersIDReinitHandler sets the operation handler for the post clusters ID reinit operation + ClusterPostClustersIDReinitHandler cluster.PostClustersIDReinitHandler + // ClusterPostClustersIDReloadHandler sets the operation handler for the post clusters ID reload operation + ClusterPostClustersIDReloadHandler cluster.PostClustersIDReloadHandler + // ClusterPostClustersIDRemoveHandler sets the operation handler for the post clusters ID remove operation + ClusterPostClustersIDRemoveHandler cluster.PostClustersIDRemoveHandler + // ClusterPostClustersIDRestartHandler sets the operation handler for the post clusters ID restart operation + ClusterPostClustersIDRestartHandler cluster.PostClustersIDRestartHandler + // ClusterPostClustersIDStartHandler sets the operation handler for the post clusters ID start operation + ClusterPostClustersIDStartHandler cluster.PostClustersIDStartHandler + // ClusterPostClustersIDStopHandler sets the operation handler for the post clusters ID stop operation + ClusterPostClustersIDStopHandler cluster.PostClustersIDStopHandler + // EnvironmentPostEnvironmentsHandler sets the operation handler for the post environments operation + EnvironmentPostEnvironmentsHandler environment.PostEnvironmentsHandler + // ProjectPostProjectsHandler sets the operation handler for the post projects operation + ProjectPostProjectsHandler project.PostProjectsHandler + // SecretPostSecretsHandler sets the operation handler for the post secrets operation + SecretPostSecretsHandler secret.PostSecretsHandler + // SettingPostSettingsHandler sets the operation handler for the post settings operation + SettingPostSettingsHandler setting.PostSettingsHandler + + // ServeError is called when an error is received, there is a default handler + // but you can set your own with this + ServeError func(http.ResponseWriter, *http.Request, error) + + // PreServerShutdown is called before the HTTP(S) server is shutdown + // This allows for custom functions to get executed before the HTTP(S) server stops accepting traffic + PreServerShutdown func() + + // ServerShutdown is called when the HTTP(S) server is shut down and done + // handling all active connections and does not accept connections any more + ServerShutdown func() + + // Custom command line argument groups with their descriptions + CommandLineOptionsGroups []swag.CommandLineOptionsGroup + + // User defined logger function. + Logger func(string, ...interface{}) +} + +// UseRedoc for documentation at /docs +func (o *PgConsoleAPI) UseRedoc() { + o.useSwaggerUI = false +} + +// UseSwaggerUI for documentation at /docs +func (o *PgConsoleAPI) UseSwaggerUI() { + o.useSwaggerUI = true +} + +// SetDefaultProduces sets the default produces media type +func (o *PgConsoleAPI) SetDefaultProduces(mediaType string) { + o.defaultProduces = mediaType +} + +// SetDefaultConsumes returns the default consumes media type +func (o *PgConsoleAPI) SetDefaultConsumes(mediaType string) { + o.defaultConsumes = mediaType +} + +// SetSpec sets a spec that will be served for the clients. +func (o *PgConsoleAPI) SetSpec(spec *loads.Document) { + o.spec = spec +} + +// DefaultProduces returns the default produces media type +func (o *PgConsoleAPI) DefaultProduces() string { + return o.defaultProduces +} + +// DefaultConsumes returns the default consumes media type +func (o *PgConsoleAPI) DefaultConsumes() string { + return o.defaultConsumes +} + +// Formats returns the registered string formats +func (o *PgConsoleAPI) Formats() strfmt.Registry { + return o.formats +} + +// RegisterFormat registers a custom format validator +func (o *PgConsoleAPI) RegisterFormat(name string, format strfmt.Format, validator strfmt.Validator) { + o.formats.Add(name, format, validator) +} + +// Validate validates the registrations in the PgConsoleAPI +func (o *PgConsoleAPI) Validate() error { + var unregistered []string + + if o.JSONConsumer == nil { + unregistered = append(unregistered, "JSONConsumer") + } + if o.PlainTextConsumer == nil { + unregistered = append(unregistered, "PlainTextConsumer") + } + + if o.JSONProducer == nil { + unregistered = append(unregistered, "JSONProducer") + } + + if o.ClusterDeleteClustersIDHandler == nil { + unregistered = append(unregistered, "cluster.DeleteClustersIDHandler") + } + if o.EnvironmentDeleteEnvironmentsIDHandler == nil { + unregistered = append(unregistered, "environment.DeleteEnvironmentsIDHandler") + } + if o.ProjectDeleteProjectsIDHandler == nil { + unregistered = append(unregistered, "project.DeleteProjectsIDHandler") + } + if o.SecretDeleteSecretsIDHandler == nil { + unregistered = append(unregistered, "secret.DeleteSecretsIDHandler") + } + if o.ClusterDeleteServersIDHandler == nil { + unregistered = append(unregistered, "cluster.DeleteServersIDHandler") + } + if o.ClusterGetClustersHandler == nil { + unregistered = append(unregistered, "cluster.GetClustersHandler") + } + if o.ClusterGetClustersDefaultNameHandler == nil { + unregistered = append(unregistered, "cluster.GetClustersDefaultNameHandler") + } + if o.ClusterGetClustersIDHandler == nil { + unregistered = append(unregistered, "cluster.GetClustersIDHandler") + } + if o.DictionaryGetDatabaseExtensionsHandler == nil { + unregistered = append(unregistered, "dictionary.GetDatabaseExtensionsHandler") + } + if o.EnvironmentGetEnvironmentsHandler == nil { + unregistered = append(unregistered, "environment.GetEnvironmentsHandler") + } + if o.DictionaryGetExternalDeploymentsHandler == nil { + unregistered = append(unregistered, "dictionary.GetExternalDeploymentsHandler") + } + if o.OperationGetOperationsHandler == nil { + unregistered = append(unregistered, "operation.GetOperationsHandler") + } + if o.OperationGetOperationsIDLogHandler == nil { + unregistered = append(unregistered, "operation.GetOperationsIDLogHandler") + } + if o.DictionaryGetPostgresVersionsHandler == nil { + unregistered = append(unregistered, "dictionary.GetPostgresVersionsHandler") + } + if o.ProjectGetProjectsHandler == nil { + unregistered = append(unregistered, "project.GetProjectsHandler") + } + if o.SecretGetSecretsHandler == nil { + unregistered = append(unregistered, "secret.GetSecretsHandler") + } + if o.SettingGetSettingsHandler == nil { + unregistered = append(unregistered, "setting.GetSettingsHandler") + } + if o.SystemGetVersionHandler == nil { + unregistered = append(unregistered, "system.GetVersionHandler") + } + if o.ProjectPatchProjectsIDHandler == nil { + unregistered = append(unregistered, "project.PatchProjectsIDHandler") + } + if o.SecretPatchSecretsIDHandler == nil { + unregistered = append(unregistered, "secret.PatchSecretsIDHandler") + } + if o.SettingPatchSettingsNameHandler == nil { + unregistered = append(unregistered, "setting.PatchSettingsNameHandler") + } + if o.ClusterPostClustersHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersHandler") + } + if o.ClusterPostClustersIDRefreshHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersIDRefreshHandler") + } + if o.ClusterPostClustersIDReinitHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersIDReinitHandler") + } + if o.ClusterPostClustersIDReloadHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersIDReloadHandler") + } + if o.ClusterPostClustersIDRemoveHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersIDRemoveHandler") + } + if o.ClusterPostClustersIDRestartHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersIDRestartHandler") + } + if o.ClusterPostClustersIDStartHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersIDStartHandler") + } + if o.ClusterPostClustersIDStopHandler == nil { + unregistered = append(unregistered, "cluster.PostClustersIDStopHandler") + } + if o.EnvironmentPostEnvironmentsHandler == nil { + unregistered = append(unregistered, "environment.PostEnvironmentsHandler") + } + if o.ProjectPostProjectsHandler == nil { + unregistered = append(unregistered, "project.PostProjectsHandler") + } + if o.SecretPostSecretsHandler == nil { + unregistered = append(unregistered, "secret.PostSecretsHandler") + } + if o.SettingPostSettingsHandler == nil { + unregistered = append(unregistered, "setting.PostSettingsHandler") + } + + if len(unregistered) > 0 { + return fmt.Errorf("missing registration: %s", strings.Join(unregistered, ", ")) + } + + return nil +} + +// ServeErrorFor gets a error handler for a given operation id +func (o *PgConsoleAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) { + return o.ServeError +} + +// AuthenticatorsFor gets the authenticators for the specified security schemes +func (o *PgConsoleAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator { + return nil +} + +// Authorizer returns the registered authorizer +func (o *PgConsoleAPI) Authorizer() runtime.Authorizer { + return nil +} + +// ConsumersFor gets the consumers for the specified media types. +// MIME type parameters are ignored here. +func (o *PgConsoleAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer { + result := make(map[string]runtime.Consumer, len(mediaTypes)) + for _, mt := range mediaTypes { + switch mt { + case "application/json": + result["application/json"] = o.JSONConsumer + case "plain/text": + result["plain/text"] = o.PlainTextConsumer + } + + if c, ok := o.customConsumers[mt]; ok { + result[mt] = c + } + } + return result +} + +// ProducersFor gets the producers for the specified media types. +// MIME type parameters are ignored here. +func (o *PgConsoleAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer { + result := make(map[string]runtime.Producer, len(mediaTypes)) + for _, mt := range mediaTypes { + switch mt { + case "application/json": + result["application/json"] = o.JSONProducer + } + + if p, ok := o.customProducers[mt]; ok { + result[mt] = p + } + } + return result +} + +// HandlerFor gets a http.Handler for the provided operation method and path +func (o *PgConsoleAPI) HandlerFor(method, path string) (http.Handler, bool) { + if o.handlers == nil { + return nil, false + } + um := strings.ToUpper(method) + if _, ok := o.handlers[um]; !ok { + return nil, false + } + if path == "/" { + path = "" + } + h, ok := o.handlers[um][path] + return h, ok +} + +// Context returns the middleware context for the pg console API +func (o *PgConsoleAPI) Context() *middleware.Context { + if o.context == nil { + o.context = middleware.NewRoutableContext(o.spec, o, nil) + } + + return o.context +} + +func (o *PgConsoleAPI) initHandlerCache() { + o.Context() // don't care about the result, just that the initialization happened + if o.handlers == nil { + o.handlers = make(map[string]map[string]http.Handler) + } + + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/clusters/{id}"] = cluster.NewDeleteClustersID(o.context, o.ClusterDeleteClustersIDHandler) + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/environments/{id}"] = environment.NewDeleteEnvironmentsID(o.context, o.EnvironmentDeleteEnvironmentsIDHandler) + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/projects/{id}"] = project.NewDeleteProjectsID(o.context, o.ProjectDeleteProjectsIDHandler) + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/secrets/{id}"] = secret.NewDeleteSecretsID(o.context, o.SecretDeleteSecretsIDHandler) + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/servers/{id}"] = cluster.NewDeleteServersID(o.context, o.ClusterDeleteServersIDHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/clusters"] = cluster.NewGetClusters(o.context, o.ClusterGetClustersHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/clusters/default_name"] = cluster.NewGetClustersDefaultName(o.context, o.ClusterGetClustersDefaultNameHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/clusters/{id}"] = cluster.NewGetClustersID(o.context, o.ClusterGetClustersIDHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/database/extensions"] = dictionary.NewGetDatabaseExtensions(o.context, o.DictionaryGetDatabaseExtensionsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/environments"] = environment.NewGetEnvironments(o.context, o.EnvironmentGetEnvironmentsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/external/deployments"] = dictionary.NewGetExternalDeployments(o.context, o.DictionaryGetExternalDeploymentsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/operations"] = operation.NewGetOperations(o.context, o.OperationGetOperationsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/operations/{id}/log"] = operation.NewGetOperationsIDLog(o.context, o.OperationGetOperationsIDLogHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/postgres_versions"] = dictionary.NewGetPostgresVersions(o.context, o.DictionaryGetPostgresVersionsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/projects"] = project.NewGetProjects(o.context, o.ProjectGetProjectsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/secrets"] = secret.NewGetSecrets(o.context, o.SecretGetSecretsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/settings"] = setting.NewGetSettings(o.context, o.SettingGetSettingsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/version"] = system.NewGetVersion(o.context, o.SystemGetVersionHandler) + if o.handlers["PATCH"] == nil { + o.handlers["PATCH"] = make(map[string]http.Handler) + } + o.handlers["PATCH"]["/projects/{id}"] = project.NewPatchProjectsID(o.context, o.ProjectPatchProjectsIDHandler) + if o.handlers["PATCH"] == nil { + o.handlers["PATCH"] = make(map[string]http.Handler) + } + o.handlers["PATCH"]["/secrets/{id}"] = secret.NewPatchSecretsID(o.context, o.SecretPatchSecretsIDHandler) + if o.handlers["PATCH"] == nil { + o.handlers["PATCH"] = make(map[string]http.Handler) + } + o.handlers["PATCH"]["/settings/{name}"] = setting.NewPatchSettingsName(o.context, o.SettingPatchSettingsNameHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters"] = cluster.NewPostClusters(o.context, o.ClusterPostClustersHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters/{id}/refresh"] = cluster.NewPostClustersIDRefresh(o.context, o.ClusterPostClustersIDRefreshHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters/{id}/reinit"] = cluster.NewPostClustersIDReinit(o.context, o.ClusterPostClustersIDReinitHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters/{id}/reload"] = cluster.NewPostClustersIDReload(o.context, o.ClusterPostClustersIDReloadHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters/{id}/remove"] = cluster.NewPostClustersIDRemove(o.context, o.ClusterPostClustersIDRemoveHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters/{id}/restart"] = cluster.NewPostClustersIDRestart(o.context, o.ClusterPostClustersIDRestartHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters/{id}/start"] = cluster.NewPostClustersIDStart(o.context, o.ClusterPostClustersIDStartHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/clusters/{id}/stop"] = cluster.NewPostClustersIDStop(o.context, o.ClusterPostClustersIDStopHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/environments"] = environment.NewPostEnvironments(o.context, o.EnvironmentPostEnvironmentsHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/projects"] = project.NewPostProjects(o.context, o.ProjectPostProjectsHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/secrets"] = secret.NewPostSecrets(o.context, o.SecretPostSecretsHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } + o.handlers["POST"]["/settings"] = setting.NewPostSettings(o.context, o.SettingPostSettingsHandler) +} + +// Serve creates a http handler to serve the API over HTTP +// can be used directly in http.ListenAndServe(":8000", api.Serve(nil)) +func (o *PgConsoleAPI) Serve(builder middleware.Builder) http.Handler { + o.Init() + + if o.Middleware != nil { + return o.Middleware(builder) + } + if o.useSwaggerUI { + return o.context.APIHandlerSwaggerUI(builder) + } + return o.context.APIHandler(builder) +} + +// Init allows you to just initialize the handler cache, you can then recompose the middleware as you see fit +func (o *PgConsoleAPI) Init() { + if len(o.handlers) == 0 { + o.initHandlerCache() + } +} + +// RegisterConsumer allows you to add (or override) a consumer for a media type. +func (o *PgConsoleAPI) RegisterConsumer(mediaType string, consumer runtime.Consumer) { + o.customConsumers[mediaType] = consumer +} + +// RegisterProducer allows you to add (or override) a producer for a media type. +func (o *PgConsoleAPI) RegisterProducer(mediaType string, producer runtime.Producer) { + o.customProducers[mediaType] = producer +} + +// AddMiddlewareFor adds a http middleware to existing handler +func (o *PgConsoleAPI) AddMiddlewareFor(method, path string, builder middleware.Builder) { + um := strings.ToUpper(method) + if path == "/" { + path = "" + } + o.Init() + if h, ok := o.handlers[um][path]; ok { + o.handlers[method][path] = builder(h) + } +} diff --git a/console/service/restapi/operations/project/delete_projects_id.go b/console/service/restapi/operations/project/delete_projects_id.go new file mode 100644 index 000000000..dbe6a80a7 --- /dev/null +++ b/console/service/restapi/operations/project/delete_projects_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// DeleteProjectsIDHandlerFunc turns a function with the right signature into a delete projects ID handler +type DeleteProjectsIDHandlerFunc func(DeleteProjectsIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteProjectsIDHandlerFunc) Handle(params DeleteProjectsIDParams) middleware.Responder { + return fn(params) +} + +// DeleteProjectsIDHandler interface for that can handle valid delete projects ID params +type DeleteProjectsIDHandler interface { + Handle(DeleteProjectsIDParams) middleware.Responder +} + +// NewDeleteProjectsID creates a new http.Handler for the delete projects ID operation +func NewDeleteProjectsID(ctx *middleware.Context, handler DeleteProjectsIDHandler) *DeleteProjectsID { + return &DeleteProjectsID{Context: ctx, Handler: handler} +} + +/* + DeleteProjectsID swagger:route DELETE /projects/{id} project deleteProjectsId + +Delete project +*/ +type DeleteProjectsID struct { + Context *middleware.Context + Handler DeleteProjectsIDHandler +} + +func (o *DeleteProjectsID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewDeleteProjectsIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/project/delete_projects_id_parameters.go b/console/service/restapi/operations/project/delete_projects_id_parameters.go new file mode 100644 index 000000000..3953d6c4d --- /dev/null +++ b/console/service/restapi/operations/project/delete_projects_id_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewDeleteProjectsIDParams creates a new DeleteProjectsIDParams object +// +// There are no default values defined in the spec. +func NewDeleteProjectsIDParams() DeleteProjectsIDParams { + + return DeleteProjectsIDParams{} +} + +// DeleteProjectsIDParams contains all the bound params for the delete projects ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters DeleteProjectsID +type DeleteProjectsIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewDeleteProjectsIDParams() beforehand. +func (o *DeleteProjectsIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *DeleteProjectsIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/project/delete_projects_id_responses.go b/console/service/restapi/operations/project/delete_projects_id_responses.go new file mode 100644 index 000000000..d5ab8e5bb --- /dev/null +++ b/console/service/restapi/operations/project/delete_projects_id_responses.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// DeleteProjectsIDNoContentCode is the HTTP code returned for type DeleteProjectsIDNoContent +const DeleteProjectsIDNoContentCode int = 204 + +/* +DeleteProjectsIDNoContent OK + +swagger:response deleteProjectsIdNoContent +*/ +type DeleteProjectsIDNoContent struct { +} + +// NewDeleteProjectsIDNoContent creates DeleteProjectsIDNoContent with default headers values +func NewDeleteProjectsIDNoContent() *DeleteProjectsIDNoContent { + + return &DeleteProjectsIDNoContent{} +} + +// WriteResponse to the client +func (o *DeleteProjectsIDNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +// DeleteProjectsIDBadRequestCode is the HTTP code returned for type DeleteProjectsIDBadRequest +const DeleteProjectsIDBadRequestCode int = 400 + +/* +DeleteProjectsIDBadRequest Error + +swagger:response deleteProjectsIdBadRequest +*/ +type DeleteProjectsIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewDeleteProjectsIDBadRequest creates DeleteProjectsIDBadRequest with default headers values +func NewDeleteProjectsIDBadRequest() *DeleteProjectsIDBadRequest { + + return &DeleteProjectsIDBadRequest{} +} + +// WithPayload adds the payload to the delete projects Id bad request response +func (o *DeleteProjectsIDBadRequest) WithPayload(payload *models.ResponseError) *DeleteProjectsIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the delete projects Id bad request response +func (o *DeleteProjectsIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *DeleteProjectsIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/project/delete_projects_id_urlbuilder.go b/console/service/restapi/operations/project/delete_projects_id_urlbuilder.go new file mode 100644 index 000000000..1f94fa20e --- /dev/null +++ b/console/service/restapi/operations/project/delete_projects_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// DeleteProjectsIDURL generates an URL for the delete projects ID operation +type DeleteProjectsIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteProjectsIDURL) WithBasePath(bp string) *DeleteProjectsIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteProjectsIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteProjectsIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/projects/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on DeleteProjectsIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *DeleteProjectsIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *DeleteProjectsIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteProjectsIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteProjectsIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteProjectsIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *DeleteProjectsIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/project/get_projects.go b/console/service/restapi/operations/project/get_projects.go new file mode 100644 index 000000000..02ecdcd1a --- /dev/null +++ b/console/service/restapi/operations/project/get_projects.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetProjectsHandlerFunc turns a function with the right signature into a get projects handler +type GetProjectsHandlerFunc func(GetProjectsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetProjectsHandlerFunc) Handle(params GetProjectsParams) middleware.Responder { + return fn(params) +} + +// GetProjectsHandler interface for that can handle valid get projects params +type GetProjectsHandler interface { + Handle(GetProjectsParams) middleware.Responder +} + +// NewGetProjects creates a new http.Handler for the get projects operation +func NewGetProjects(ctx *middleware.Context, handler GetProjectsHandler) *GetProjects { + return &GetProjects{Context: ctx, Handler: handler} +} + +/* + GetProjects swagger:route GET /projects project getProjects + +Get projects list +*/ +type GetProjects struct { + Context *middleware.Context + Handler GetProjectsHandler +} + +func (o *GetProjects) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetProjectsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/project/get_projects_parameters.go b/console/service/restapi/operations/project/get_projects_parameters.go new file mode 100644 index 000000000..b03004fd7 --- /dev/null +++ b/console/service/restapi/operations/project/get_projects_parameters.go @@ -0,0 +1,115 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetProjectsParams creates a new GetProjectsParams object +// +// There are no default values defined in the spec. +func NewGetProjectsParams() GetProjectsParams { + + return GetProjectsParams{} +} + +// GetProjectsParams contains all the bound params for the get projects operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetProjects +type GetProjectsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + Limit *int64 + /* + In: query + */ + Offset *int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetProjectsParams() beforehand. +func (o *GetProjectsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetProjectsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetProjectsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} diff --git a/console/service/restapi/operations/project/get_projects_responses.go b/console/service/restapi/operations/project/get_projects_responses.go new file mode 100644 index 000000000..93aedd3bf --- /dev/null +++ b/console/service/restapi/operations/project/get_projects_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetProjectsOKCode is the HTTP code returned for type GetProjectsOK +const GetProjectsOKCode int = 200 + +/* +GetProjectsOK OK + +swagger:response getProjectsOK +*/ +type GetProjectsOK struct { + + /* + In: Body + */ + Payload *models.ResponseProjectsList `json:"body,omitempty"` +} + +// NewGetProjectsOK creates GetProjectsOK with default headers values +func NewGetProjectsOK() *GetProjectsOK { + + return &GetProjectsOK{} +} + +// WithPayload adds the payload to the get projects o k response +func (o *GetProjectsOK) WithPayload(payload *models.ResponseProjectsList) *GetProjectsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get projects o k response +func (o *GetProjectsOK) SetPayload(payload *models.ResponseProjectsList) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetProjectsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetProjectsBadRequestCode is the HTTP code returned for type GetProjectsBadRequest +const GetProjectsBadRequestCode int = 400 + +/* +GetProjectsBadRequest Error + +swagger:response getProjectsBadRequest +*/ +type GetProjectsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetProjectsBadRequest creates GetProjectsBadRequest with default headers values +func NewGetProjectsBadRequest() *GetProjectsBadRequest { + + return &GetProjectsBadRequest{} +} + +// WithPayload adds the payload to the get projects bad request response +func (o *GetProjectsBadRequest) WithPayload(payload *models.ResponseError) *GetProjectsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get projects bad request response +func (o *GetProjectsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetProjectsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/project/get_projects_urlbuilder.go b/console/service/restapi/operations/project/get_projects_urlbuilder.go new file mode 100644 index 000000000..933286efe --- /dev/null +++ b/console/service/restapi/operations/project/get_projects_urlbuilder.go @@ -0,0 +1,114 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetProjectsURL generates an URL for the get projects operation +type GetProjectsURL struct { + Limit *int64 + Offset *int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetProjectsURL) WithBasePath(bp string) *GetProjectsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetProjectsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetProjectsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/projects" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetProjectsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetProjectsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetProjectsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetProjectsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetProjectsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetProjectsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/project/patch_projects_id.go b/console/service/restapi/operations/project/patch_projects_id.go new file mode 100644 index 000000000..d8573af8e --- /dev/null +++ b/console/service/restapi/operations/project/patch_projects_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PatchProjectsIDHandlerFunc turns a function with the right signature into a patch projects ID handler +type PatchProjectsIDHandlerFunc func(PatchProjectsIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PatchProjectsIDHandlerFunc) Handle(params PatchProjectsIDParams) middleware.Responder { + return fn(params) +} + +// PatchProjectsIDHandler interface for that can handle valid patch projects ID params +type PatchProjectsIDHandler interface { + Handle(PatchProjectsIDParams) middleware.Responder +} + +// NewPatchProjectsID creates a new http.Handler for the patch projects ID operation +func NewPatchProjectsID(ctx *middleware.Context, handler PatchProjectsIDHandler) *PatchProjectsID { + return &PatchProjectsID{Context: ctx, Handler: handler} +} + +/* + PatchProjectsID swagger:route PATCH /projects/{id} project patchProjectsId + +Change project +*/ +type PatchProjectsID struct { + Context *middleware.Context + Handler PatchProjectsIDHandler +} + +func (o *PatchProjectsID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPatchProjectsIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/project/patch_projects_id_parameters.go b/console/service/restapi/operations/project/patch_projects_id_parameters.go new file mode 100644 index 000000000..d454b9e17 --- /dev/null +++ b/console/service/restapi/operations/project/patch_projects_id_parameters.go @@ -0,0 +1,115 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPatchProjectsIDParams creates a new PatchProjectsIDParams object +// +// There are no default values defined in the spec. +func NewPatchProjectsIDParams() PatchProjectsIDParams { + + return PatchProjectsIDParams{} +} + +// PatchProjectsIDParams contains all the bound params for the patch projects ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters PatchProjectsID +type PatchProjectsIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestProjectPatch + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPatchProjectsIDParams() beforehand. +func (o *PatchProjectsIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestProjectPatch + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PatchProjectsIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/project/patch_projects_id_responses.go b/console/service/restapi/operations/project/patch_projects_id_responses.go new file mode 100644 index 000000000..1c042a293 --- /dev/null +++ b/console/service/restapi/operations/project/patch_projects_id_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PatchProjectsIDOKCode is the HTTP code returned for type PatchProjectsIDOK +const PatchProjectsIDOKCode int = 200 + +/* +PatchProjectsIDOK OK + +swagger:response patchProjectsIdOK +*/ +type PatchProjectsIDOK struct { + + /* + In: Body + */ + Payload *models.ResponseProject `json:"body,omitempty"` +} + +// NewPatchProjectsIDOK creates PatchProjectsIDOK with default headers values +func NewPatchProjectsIDOK() *PatchProjectsIDOK { + + return &PatchProjectsIDOK{} +} + +// WithPayload adds the payload to the patch projects Id o k response +func (o *PatchProjectsIDOK) WithPayload(payload *models.ResponseProject) *PatchProjectsIDOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the patch projects Id o k response +func (o *PatchProjectsIDOK) SetPayload(payload *models.ResponseProject) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PatchProjectsIDOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PatchProjectsIDBadRequestCode is the HTTP code returned for type PatchProjectsIDBadRequest +const PatchProjectsIDBadRequestCode int = 400 + +/* +PatchProjectsIDBadRequest Error + +swagger:response patchProjectsIdBadRequest +*/ +type PatchProjectsIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPatchProjectsIDBadRequest creates PatchProjectsIDBadRequest with default headers values +func NewPatchProjectsIDBadRequest() *PatchProjectsIDBadRequest { + + return &PatchProjectsIDBadRequest{} +} + +// WithPayload adds the payload to the patch projects Id bad request response +func (o *PatchProjectsIDBadRequest) WithPayload(payload *models.ResponseError) *PatchProjectsIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the patch projects Id bad request response +func (o *PatchProjectsIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PatchProjectsIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/project/patch_projects_id_urlbuilder.go b/console/service/restapi/operations/project/patch_projects_id_urlbuilder.go new file mode 100644 index 000000000..1c9230303 --- /dev/null +++ b/console/service/restapi/operations/project/patch_projects_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PatchProjectsIDURL generates an URL for the patch projects ID operation +type PatchProjectsIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PatchProjectsIDURL) WithBasePath(bp string) *PatchProjectsIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PatchProjectsIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PatchProjectsIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/projects/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PatchProjectsIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PatchProjectsIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PatchProjectsIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PatchProjectsIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PatchProjectsIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PatchProjectsIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PatchProjectsIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/project/post_projects.go b/console/service/restapi/operations/project/post_projects.go new file mode 100644 index 000000000..a958178da --- /dev/null +++ b/console/service/restapi/operations/project/post_projects.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostProjectsHandlerFunc turns a function with the right signature into a post projects handler +type PostProjectsHandlerFunc func(PostProjectsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostProjectsHandlerFunc) Handle(params PostProjectsParams) middleware.Responder { + return fn(params) +} + +// PostProjectsHandler interface for that can handle valid post projects params +type PostProjectsHandler interface { + Handle(PostProjectsParams) middleware.Responder +} + +// NewPostProjects creates a new http.Handler for the post projects operation +func NewPostProjects(ctx *middleware.Context, handler PostProjectsHandler) *PostProjects { + return &PostProjects{Context: ctx, Handler: handler} +} + +/* + PostProjects swagger:route POST /projects project postProjects + +Create new project +*/ +type PostProjects struct { + Context *middleware.Context + Handler PostProjectsHandler +} + +func (o *PostProjects) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostProjectsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/project/post_projects_parameters.go b/console/service/restapi/operations/project/post_projects_parameters.go new file mode 100644 index 000000000..e3cf204fa --- /dev/null +++ b/console/service/restapi/operations/project/post_projects_parameters.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPostProjectsParams creates a new PostProjectsParams object +// +// There are no default values defined in the spec. +func NewPostProjectsParams() PostProjectsParams { + + return PostProjectsParams{} +} + +// PostProjectsParams contains all the bound params for the post projects operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostProjects +type PostProjectsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestProjectCreate +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostProjectsParams() beforehand. +func (o *PostProjectsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestProjectCreate + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/project/post_projects_responses.go b/console/service/restapi/operations/project/post_projects_responses.go new file mode 100644 index 000000000..505ea560b --- /dev/null +++ b/console/service/restapi/operations/project/post_projects_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostProjectsOKCode is the HTTP code returned for type PostProjectsOK +const PostProjectsOKCode int = 200 + +/* +PostProjectsOK OK + +swagger:response postProjectsOK +*/ +type PostProjectsOK struct { + + /* + In: Body + */ + Payload *models.ResponseProject `json:"body,omitempty"` +} + +// NewPostProjectsOK creates PostProjectsOK with default headers values +func NewPostProjectsOK() *PostProjectsOK { + + return &PostProjectsOK{} +} + +// WithPayload adds the payload to the post projects o k response +func (o *PostProjectsOK) WithPayload(payload *models.ResponseProject) *PostProjectsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post projects o k response +func (o *PostProjectsOK) SetPayload(payload *models.ResponseProject) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostProjectsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostProjectsBadRequestCode is the HTTP code returned for type PostProjectsBadRequest +const PostProjectsBadRequestCode int = 400 + +/* +PostProjectsBadRequest Error + +swagger:response postProjectsBadRequest +*/ +type PostProjectsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostProjectsBadRequest creates PostProjectsBadRequest with default headers values +func NewPostProjectsBadRequest() *PostProjectsBadRequest { + + return &PostProjectsBadRequest{} +} + +// WithPayload adds the payload to the post projects bad request response +func (o *PostProjectsBadRequest) WithPayload(payload *models.ResponseError) *PostProjectsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post projects bad request response +func (o *PostProjectsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostProjectsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/project/post_projects_urlbuilder.go b/console/service/restapi/operations/project/post_projects_urlbuilder.go new file mode 100644 index 000000000..3d9707909 --- /dev/null +++ b/console/service/restapi/operations/project/post_projects_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package project + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// PostProjectsURL generates an URL for the post projects operation +type PostProjectsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostProjectsURL) WithBasePath(bp string) *PostProjectsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostProjectsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostProjectsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/projects" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostProjectsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostProjectsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostProjectsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostProjectsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostProjectsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostProjectsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/secret/delete_secrets_id.go b/console/service/restapi/operations/secret/delete_secrets_id.go new file mode 100644 index 000000000..7ef6ad789 --- /dev/null +++ b/console/service/restapi/operations/secret/delete_secrets_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// DeleteSecretsIDHandlerFunc turns a function with the right signature into a delete secrets ID handler +type DeleteSecretsIDHandlerFunc func(DeleteSecretsIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn DeleteSecretsIDHandlerFunc) Handle(params DeleteSecretsIDParams) middleware.Responder { + return fn(params) +} + +// DeleteSecretsIDHandler interface for that can handle valid delete secrets ID params +type DeleteSecretsIDHandler interface { + Handle(DeleteSecretsIDParams) middleware.Responder +} + +// NewDeleteSecretsID creates a new http.Handler for the delete secrets ID operation +func NewDeleteSecretsID(ctx *middleware.Context, handler DeleteSecretsIDHandler) *DeleteSecretsID { + return &DeleteSecretsID{Context: ctx, Handler: handler} +} + +/* + DeleteSecretsID swagger:route DELETE /secrets/{id} secret deleteSecretsId + +Delete secret +*/ +type DeleteSecretsID struct { + Context *middleware.Context + Handler DeleteSecretsIDHandler +} + +func (o *DeleteSecretsID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewDeleteSecretsIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/secret/delete_secrets_id_parameters.go b/console/service/restapi/operations/secret/delete_secrets_id_parameters.go new file mode 100644 index 000000000..e3fc672c7 --- /dev/null +++ b/console/service/restapi/operations/secret/delete_secrets_id_parameters.go @@ -0,0 +1,77 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewDeleteSecretsIDParams creates a new DeleteSecretsIDParams object +// +// There are no default values defined in the spec. +func NewDeleteSecretsIDParams() DeleteSecretsIDParams { + + return DeleteSecretsIDParams{} +} + +// DeleteSecretsIDParams contains all the bound params for the delete secrets ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters DeleteSecretsID +type DeleteSecretsIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewDeleteSecretsIDParams() beforehand. +func (o *DeleteSecretsIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *DeleteSecretsIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/secret/delete_secrets_id_responses.go b/console/service/restapi/operations/secret/delete_secrets_id_responses.go new file mode 100644 index 000000000..c91f29e5d --- /dev/null +++ b/console/service/restapi/operations/secret/delete_secrets_id_responses.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// DeleteSecretsIDNoContentCode is the HTTP code returned for type DeleteSecretsIDNoContent +const DeleteSecretsIDNoContentCode int = 204 + +/* +DeleteSecretsIDNoContent OK + +swagger:response deleteSecretsIdNoContent +*/ +type DeleteSecretsIDNoContent struct { +} + +// NewDeleteSecretsIDNoContent creates DeleteSecretsIDNoContent with default headers values +func NewDeleteSecretsIDNoContent() *DeleteSecretsIDNoContent { + + return &DeleteSecretsIDNoContent{} +} + +// WriteResponse to the client +func (o *DeleteSecretsIDNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(204) +} + +// DeleteSecretsIDBadRequestCode is the HTTP code returned for type DeleteSecretsIDBadRequest +const DeleteSecretsIDBadRequestCode int = 400 + +/* +DeleteSecretsIDBadRequest Error + +swagger:response deleteSecretsIdBadRequest +*/ +type DeleteSecretsIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewDeleteSecretsIDBadRequest creates DeleteSecretsIDBadRequest with default headers values +func NewDeleteSecretsIDBadRequest() *DeleteSecretsIDBadRequest { + + return &DeleteSecretsIDBadRequest{} +} + +// WithPayload adds the payload to the delete secrets Id bad request response +func (o *DeleteSecretsIDBadRequest) WithPayload(payload *models.ResponseError) *DeleteSecretsIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the delete secrets Id bad request response +func (o *DeleteSecretsIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *DeleteSecretsIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/secret/delete_secrets_id_urlbuilder.go b/console/service/restapi/operations/secret/delete_secrets_id_urlbuilder.go new file mode 100644 index 000000000..a4fc9de64 --- /dev/null +++ b/console/service/restapi/operations/secret/delete_secrets_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// DeleteSecretsIDURL generates an URL for the delete secrets ID operation +type DeleteSecretsIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteSecretsIDURL) WithBasePath(bp string) *DeleteSecretsIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *DeleteSecretsIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *DeleteSecretsIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/secrets/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on DeleteSecretsIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *DeleteSecretsIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *DeleteSecretsIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *DeleteSecretsIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on DeleteSecretsIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on DeleteSecretsIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *DeleteSecretsIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/secret/get_secrets.go b/console/service/restapi/operations/secret/get_secrets.go new file mode 100644 index 000000000..7f261765a --- /dev/null +++ b/console/service/restapi/operations/secret/get_secrets.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetSecretsHandlerFunc turns a function with the right signature into a get secrets handler +type GetSecretsHandlerFunc func(GetSecretsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetSecretsHandlerFunc) Handle(params GetSecretsParams) middleware.Responder { + return fn(params) +} + +// GetSecretsHandler interface for that can handle valid get secrets params +type GetSecretsHandler interface { + Handle(GetSecretsParams) middleware.Responder +} + +// NewGetSecrets creates a new http.Handler for the get secrets operation +func NewGetSecrets(ctx *middleware.Context, handler GetSecretsHandler) *GetSecrets { + return &GetSecrets{Context: ctx, Handler: handler} +} + +/* + GetSecrets swagger:route GET /secrets secret getSecrets + +Get secrets list +*/ +type GetSecrets struct { + Context *middleware.Context + Handler GetSecretsHandler +} + +func (o *GetSecrets) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetSecretsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/secret/get_secrets_parameters.go b/console/service/restapi/operations/secret/get_secrets_parameters.go new file mode 100644 index 000000000..c78f008f3 --- /dev/null +++ b/console/service/restapi/operations/secret/get_secrets_parameters.go @@ -0,0 +1,233 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewGetSecretsParams creates a new GetSecretsParams object +// +// There are no default values defined in the spec. +func NewGetSecretsParams() GetSecretsParams { + + return GetSecretsParams{} +} + +// GetSecretsParams contains all the bound params for the get secrets operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetSecrets +type GetSecretsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + Limit *int64 + /*Filter by name + In: query + */ + Name *string + /* + In: query + */ + Offset *int64 + /* + Required: true + In: query + */ + ProjectID int64 + /*Sort by fields. Example: sort_by=id,name,-type,created_at,updated_at + In: query + */ + SortBy *string + /*Filter by type + In: query + */ + Type *string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetSecretsParams() beforehand. +func (o *GetSecretsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qName, qhkName, _ := qs.GetOK("name") + if err := o.bindName(qName, qhkName, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + + qProjectID, qhkProjectID, _ := qs.GetOK("project_id") + if err := o.bindProjectID(qProjectID, qhkProjectID, route.Formats); err != nil { + res = append(res, err) + } + + qSortBy, qhkSortBy, _ := qs.GetOK("sort_by") + if err := o.bindSortBy(qSortBy, qhkSortBy, route.Formats); err != nil { + res = append(res, err) + } + + qType, qhkType, _ := qs.GetOK("type") + if err := o.bindType(qType, qhkType, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetSecretsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindName binds and validates parameter Name from query. +func (o *GetSecretsParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Name = &raw + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetSecretsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} + +// bindProjectID binds and validates parameter ProjectID from query. +func (o *GetSecretsParams) bindProjectID(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("project_id", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("project_id", "query", raw); err != nil { + return err + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("project_id", "query", "int64", raw) + } + o.ProjectID = value + + return nil +} + +// bindSortBy binds and validates parameter SortBy from query. +func (o *GetSecretsParams) bindSortBy(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.SortBy = &raw + + return nil +} + +// bindType binds and validates parameter Type from query. +func (o *GetSecretsParams) bindType(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Type = &raw + + return nil +} diff --git a/console/service/restapi/operations/secret/get_secrets_responses.go b/console/service/restapi/operations/secret/get_secrets_responses.go new file mode 100644 index 000000000..77aab0ece --- /dev/null +++ b/console/service/restapi/operations/secret/get_secrets_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetSecretsOKCode is the HTTP code returned for type GetSecretsOK +const GetSecretsOKCode int = 200 + +/* +GetSecretsOK OK + +swagger:response getSecretsOK +*/ +type GetSecretsOK struct { + + /* + In: Body + */ + Payload *models.ResponseSecretInfoList `json:"body,omitempty"` +} + +// NewGetSecretsOK creates GetSecretsOK with default headers values +func NewGetSecretsOK() *GetSecretsOK { + + return &GetSecretsOK{} +} + +// WithPayload adds the payload to the get secrets o k response +func (o *GetSecretsOK) WithPayload(payload *models.ResponseSecretInfoList) *GetSecretsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get secrets o k response +func (o *GetSecretsOK) SetPayload(payload *models.ResponseSecretInfoList) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetSecretsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetSecretsBadRequestCode is the HTTP code returned for type GetSecretsBadRequest +const GetSecretsBadRequestCode int = 400 + +/* +GetSecretsBadRequest Error + +swagger:response getSecretsBadRequest +*/ +type GetSecretsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetSecretsBadRequest creates GetSecretsBadRequest with default headers values +func NewGetSecretsBadRequest() *GetSecretsBadRequest { + + return &GetSecretsBadRequest{} +} + +// WithPayload adds the payload to the get secrets bad request response +func (o *GetSecretsBadRequest) WithPayload(payload *models.ResponseError) *GetSecretsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get secrets bad request response +func (o *GetSecretsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetSecretsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/secret/get_secrets_urlbuilder.go b/console/service/restapi/operations/secret/get_secrets_urlbuilder.go new file mode 100644 index 000000000..f337fbcf1 --- /dev/null +++ b/console/service/restapi/operations/secret/get_secrets_urlbuilder.go @@ -0,0 +1,147 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetSecretsURL generates an URL for the get secrets operation +type GetSecretsURL struct { + Limit *int64 + Name *string + Offset *int64 + ProjectID int64 + SortBy *string + Type *string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetSecretsURL) WithBasePath(bp string) *GetSecretsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetSecretsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetSecretsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/secrets" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var nameQ string + if o.Name != nil { + nameQ = *o.Name + } + if nameQ != "" { + qs.Set("name", nameQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + projectIDQ := swag.FormatInt64(o.ProjectID) + if projectIDQ != "" { + qs.Set("project_id", projectIDQ) + } + + var sortByQ string + if o.SortBy != nil { + sortByQ = *o.SortBy + } + if sortByQ != "" { + qs.Set("sort_by", sortByQ) + } + + var typeVarQ string + if o.Type != nil { + typeVarQ = *o.Type + } + if typeVarQ != "" { + qs.Set("type", typeVarQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetSecretsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetSecretsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetSecretsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetSecretsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetSecretsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetSecretsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/secret/patch_secrets_id.go b/console/service/restapi/operations/secret/patch_secrets_id.go new file mode 100644 index 000000000..e9b3aea58 --- /dev/null +++ b/console/service/restapi/operations/secret/patch_secrets_id.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PatchSecretsIDHandlerFunc turns a function with the right signature into a patch secrets ID handler +type PatchSecretsIDHandlerFunc func(PatchSecretsIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PatchSecretsIDHandlerFunc) Handle(params PatchSecretsIDParams) middleware.Responder { + return fn(params) +} + +// PatchSecretsIDHandler interface for that can handle valid patch secrets ID params +type PatchSecretsIDHandler interface { + Handle(PatchSecretsIDParams) middleware.Responder +} + +// NewPatchSecretsID creates a new http.Handler for the patch secrets ID operation +func NewPatchSecretsID(ctx *middleware.Context, handler PatchSecretsIDHandler) *PatchSecretsID { + return &PatchSecretsID{Context: ctx, Handler: handler} +} + +/* + PatchSecretsID swagger:route PATCH /secrets/{id} secret patchSecretsId + +Change secret +*/ +type PatchSecretsID struct { + Context *middleware.Context + Handler PatchSecretsIDHandler +} + +func (o *PatchSecretsID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPatchSecretsIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/secret/patch_secrets_id_parameters.go b/console/service/restapi/operations/secret/patch_secrets_id_parameters.go new file mode 100644 index 000000000..00ac16952 --- /dev/null +++ b/console/service/restapi/operations/secret/patch_secrets_id_parameters.go @@ -0,0 +1,115 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPatchSecretsIDParams creates a new PatchSecretsIDParams object +// +// There are no default values defined in the spec. +func NewPatchSecretsIDParams() PatchSecretsIDParams { + + return PatchSecretsIDParams{} +} + +// PatchSecretsIDParams contains all the bound params for the patch secrets ID operation +// typically these are obtained from a http.Request +// +// swagger:parameters PatchSecretsID +type PatchSecretsIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestSecretPatch + /* + Required: true + In: path + */ + ID int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPatchSecretsIDParams() beforehand. +func (o *PatchSecretsIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestSecretPatch + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rID, rhkID, _ := route.Params.GetOK("id") + if err := o.bindID(rID, rhkID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindID binds and validates parameter ID from path. +func (o *PatchSecretsIDParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("id", "path", "int64", raw) + } + o.ID = value + + return nil +} diff --git a/console/service/restapi/operations/secret/patch_secrets_id_responses.go b/console/service/restapi/operations/secret/patch_secrets_id_responses.go new file mode 100644 index 000000000..41c1b3f5a --- /dev/null +++ b/console/service/restapi/operations/secret/patch_secrets_id_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PatchSecretsIDOKCode is the HTTP code returned for type PatchSecretsIDOK +const PatchSecretsIDOKCode int = 200 + +/* +PatchSecretsIDOK OK + +swagger:response patchSecretsIdOK +*/ +type PatchSecretsIDOK struct { + + /* + In: Body + */ + Payload *models.ResponseSecretInfo `json:"body,omitempty"` +} + +// NewPatchSecretsIDOK creates PatchSecretsIDOK with default headers values +func NewPatchSecretsIDOK() *PatchSecretsIDOK { + + return &PatchSecretsIDOK{} +} + +// WithPayload adds the payload to the patch secrets Id o k response +func (o *PatchSecretsIDOK) WithPayload(payload *models.ResponseSecretInfo) *PatchSecretsIDOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the patch secrets Id o k response +func (o *PatchSecretsIDOK) SetPayload(payload *models.ResponseSecretInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PatchSecretsIDOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PatchSecretsIDBadRequestCode is the HTTP code returned for type PatchSecretsIDBadRequest +const PatchSecretsIDBadRequestCode int = 400 + +/* +PatchSecretsIDBadRequest Error + +swagger:response patchSecretsIdBadRequest +*/ +type PatchSecretsIDBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPatchSecretsIDBadRequest creates PatchSecretsIDBadRequest with default headers values +func NewPatchSecretsIDBadRequest() *PatchSecretsIDBadRequest { + + return &PatchSecretsIDBadRequest{} +} + +// WithPayload adds the payload to the patch secrets Id bad request response +func (o *PatchSecretsIDBadRequest) WithPayload(payload *models.ResponseError) *PatchSecretsIDBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the patch secrets Id bad request response +func (o *PatchSecretsIDBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PatchSecretsIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/secret/patch_secrets_id_urlbuilder.go b/console/service/restapi/operations/secret/patch_secrets_id_urlbuilder.go new file mode 100644 index 000000000..ead2dfecc --- /dev/null +++ b/console/service/restapi/operations/secret/patch_secrets_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// PatchSecretsIDURL generates an URL for the patch secrets ID operation +type PatchSecretsIDURL struct { + ID int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PatchSecretsIDURL) WithBasePath(bp string) *PatchSecretsIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PatchSecretsIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PatchSecretsIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/secrets/{id}" + + id := swag.FormatInt64(o.ID) + if id != "" { + _path = strings.Replace(_path, "{id}", id, -1) + } else { + return nil, errors.New("id is required on PatchSecretsIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PatchSecretsIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PatchSecretsIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PatchSecretsIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PatchSecretsIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PatchSecretsIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PatchSecretsIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/secret/post_secrets.go b/console/service/restapi/operations/secret/post_secrets.go new file mode 100644 index 000000000..13d9866a5 --- /dev/null +++ b/console/service/restapi/operations/secret/post_secrets.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostSecretsHandlerFunc turns a function with the right signature into a post secrets handler +type PostSecretsHandlerFunc func(PostSecretsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostSecretsHandlerFunc) Handle(params PostSecretsParams) middleware.Responder { + return fn(params) +} + +// PostSecretsHandler interface for that can handle valid post secrets params +type PostSecretsHandler interface { + Handle(PostSecretsParams) middleware.Responder +} + +// NewPostSecrets creates a new http.Handler for the post secrets operation +func NewPostSecrets(ctx *middleware.Context, handler PostSecretsHandler) *PostSecrets { + return &PostSecrets{Context: ctx, Handler: handler} +} + +/* + PostSecrets swagger:route POST /secrets secret postSecrets + +Create new secret +*/ +type PostSecrets struct { + Context *middleware.Context + Handler PostSecretsHandler +} + +func (o *PostSecrets) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostSecretsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/secret/post_secrets_parameters.go b/console/service/restapi/operations/secret/post_secrets_parameters.go new file mode 100644 index 000000000..5280ee863 --- /dev/null +++ b/console/service/restapi/operations/secret/post_secrets_parameters.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPostSecretsParams creates a new PostSecretsParams object +// +// There are no default values defined in the spec. +func NewPostSecretsParams() PostSecretsParams { + + return PostSecretsParams{} +} + +// PostSecretsParams contains all the bound params for the post secrets operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostSecrets +type PostSecretsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestSecretCreate +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostSecretsParams() beforehand. +func (o *PostSecretsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestSecretCreate + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/secret/post_secrets_responses.go b/console/service/restapi/operations/secret/post_secrets_responses.go new file mode 100644 index 000000000..075afc486 --- /dev/null +++ b/console/service/restapi/operations/secret/post_secrets_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostSecretsOKCode is the HTTP code returned for type PostSecretsOK +const PostSecretsOKCode int = 200 + +/* +PostSecretsOK OK + +swagger:response postSecretsOK +*/ +type PostSecretsOK struct { + + /* + In: Body + */ + Payload *models.ResponseSecretInfo `json:"body,omitempty"` +} + +// NewPostSecretsOK creates PostSecretsOK with default headers values +func NewPostSecretsOK() *PostSecretsOK { + + return &PostSecretsOK{} +} + +// WithPayload adds the payload to the post secrets o k response +func (o *PostSecretsOK) WithPayload(payload *models.ResponseSecretInfo) *PostSecretsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post secrets o k response +func (o *PostSecretsOK) SetPayload(payload *models.ResponseSecretInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostSecretsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostSecretsBadRequestCode is the HTTP code returned for type PostSecretsBadRequest +const PostSecretsBadRequestCode int = 400 + +/* +PostSecretsBadRequest Error + +swagger:response postSecretsBadRequest +*/ +type PostSecretsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostSecretsBadRequest creates PostSecretsBadRequest with default headers values +func NewPostSecretsBadRequest() *PostSecretsBadRequest { + + return &PostSecretsBadRequest{} +} + +// WithPayload adds the payload to the post secrets bad request response +func (o *PostSecretsBadRequest) WithPayload(payload *models.ResponseError) *PostSecretsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post secrets bad request response +func (o *PostSecretsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostSecretsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/secret/post_secrets_urlbuilder.go b/console/service/restapi/operations/secret/post_secrets_urlbuilder.go new file mode 100644 index 000000000..af303024f --- /dev/null +++ b/console/service/restapi/operations/secret/post_secrets_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package secret + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// PostSecretsURL generates an URL for the post secrets operation +type PostSecretsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostSecretsURL) WithBasePath(bp string) *PostSecretsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostSecretsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostSecretsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/secrets" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostSecretsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostSecretsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostSecretsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostSecretsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostSecretsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostSecretsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/setting/get_settings.go b/console/service/restapi/operations/setting/get_settings.go new file mode 100644 index 000000000..94aad170e --- /dev/null +++ b/console/service/restapi/operations/setting/get_settings.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetSettingsHandlerFunc turns a function with the right signature into a get settings handler +type GetSettingsHandlerFunc func(GetSettingsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetSettingsHandlerFunc) Handle(params GetSettingsParams) middleware.Responder { + return fn(params) +} + +// GetSettingsHandler interface for that can handle valid get settings params +type GetSettingsHandler interface { + Handle(GetSettingsParams) middleware.Responder +} + +// NewGetSettings creates a new http.Handler for the get settings operation +func NewGetSettings(ctx *middleware.Context, handler GetSettingsHandler) *GetSettings { + return &GetSettings{Context: ctx, Handler: handler} +} + +/* + GetSettings swagger:route GET /settings setting getSettings + +Get settings +*/ +type GetSettings struct { + Context *middleware.Context + Handler GetSettingsHandler +} + +func (o *GetSettings) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetSettingsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/setting/get_settings_parameters.go b/console/service/restapi/operations/setting/get_settings_parameters.go new file mode 100644 index 000000000..b4b6e5860 --- /dev/null +++ b/console/service/restapi/operations/setting/get_settings_parameters.go @@ -0,0 +1,142 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetSettingsParams creates a new GetSettingsParams object +// +// There are no default values defined in the spec. +func NewGetSettingsParams() GetSettingsParams { + + return GetSettingsParams{} +} + +// GetSettingsParams contains all the bound params for the get settings operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetSettings +type GetSettingsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + Limit *int64 + /*Filter by name + In: query + */ + Name *string + /* + In: query + */ + Offset *int64 +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetSettingsParams() beforehand. +func (o *GetSettingsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qLimit, qhkLimit, _ := qs.GetOK("limit") + if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil { + res = append(res, err) + } + + qName, qhkName, _ := qs.GetOK("name") + if err := o.bindName(qName, qhkName, route.Formats); err != nil { + res = append(res, err) + } + + qOffset, qhkOffset, _ := qs.GetOK("offset") + if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindLimit binds and validates parameter Limit from query. +func (o *GetSettingsParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("limit", "query", "int64", raw) + } + o.Limit = &value + + return nil +} + +// bindName binds and validates parameter Name from query. +func (o *GetSettingsParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Name = &raw + + return nil +} + +// bindOffset binds and validates parameter Offset from query. +func (o *GetSettingsParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("offset", "query", "int64", raw) + } + o.Offset = &value + + return nil +} diff --git a/console/service/restapi/operations/setting/get_settings_responses.go b/console/service/restapi/operations/setting/get_settings_responses.go new file mode 100644 index 000000000..17bd7095a --- /dev/null +++ b/console/service/restapi/operations/setting/get_settings_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetSettingsOKCode is the HTTP code returned for type GetSettingsOK +const GetSettingsOKCode int = 200 + +/* +GetSettingsOK OK + +swagger:response getSettingsOK +*/ +type GetSettingsOK struct { + + /* + In: Body + */ + Payload *models.ResponseSettings `json:"body,omitempty"` +} + +// NewGetSettingsOK creates GetSettingsOK with default headers values +func NewGetSettingsOK() *GetSettingsOK { + + return &GetSettingsOK{} +} + +// WithPayload adds the payload to the get settings o k response +func (o *GetSettingsOK) WithPayload(payload *models.ResponseSettings) *GetSettingsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get settings o k response +func (o *GetSettingsOK) SetPayload(payload *models.ResponseSettings) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetSettingsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetSettingsBadRequestCode is the HTTP code returned for type GetSettingsBadRequest +const GetSettingsBadRequestCode int = 400 + +/* +GetSettingsBadRequest Error + +swagger:response getSettingsBadRequest +*/ +type GetSettingsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewGetSettingsBadRequest creates GetSettingsBadRequest with default headers values +func NewGetSettingsBadRequest() *GetSettingsBadRequest { + + return &GetSettingsBadRequest{} +} + +// WithPayload adds the payload to the get settings bad request response +func (o *GetSettingsBadRequest) WithPayload(payload *models.ResponseError) *GetSettingsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get settings bad request response +func (o *GetSettingsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetSettingsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/setting/get_settings_urlbuilder.go b/console/service/restapi/operations/setting/get_settings_urlbuilder.go new file mode 100644 index 000000000..7c9c5f8da --- /dev/null +++ b/console/service/restapi/operations/setting/get_settings_urlbuilder.go @@ -0,0 +1,123 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetSettingsURL generates an URL for the get settings operation +type GetSettingsURL struct { + Limit *int64 + Name *string + Offset *int64 + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetSettingsURL) WithBasePath(bp string) *GetSettingsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetSettingsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetSettingsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/settings" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var limitQ string + if o.Limit != nil { + limitQ = swag.FormatInt64(*o.Limit) + } + if limitQ != "" { + qs.Set("limit", limitQ) + } + + var nameQ string + if o.Name != nil { + nameQ = *o.Name + } + if nameQ != "" { + qs.Set("name", nameQ) + } + + var offsetQ string + if o.Offset != nil { + offsetQ = swag.FormatInt64(*o.Offset) + } + if offsetQ != "" { + qs.Set("offset", offsetQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetSettingsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetSettingsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetSettingsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetSettingsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetSettingsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetSettingsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/setting/patch_settings_name.go b/console/service/restapi/operations/setting/patch_settings_name.go new file mode 100644 index 000000000..4a8bd83b0 --- /dev/null +++ b/console/service/restapi/operations/setting/patch_settings_name.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PatchSettingsNameHandlerFunc turns a function with the right signature into a patch settings name handler +type PatchSettingsNameHandlerFunc func(PatchSettingsNameParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PatchSettingsNameHandlerFunc) Handle(params PatchSettingsNameParams) middleware.Responder { + return fn(params) +} + +// PatchSettingsNameHandler interface for that can handle valid patch settings name params +type PatchSettingsNameHandler interface { + Handle(PatchSettingsNameParams) middleware.Responder +} + +// NewPatchSettingsName creates a new http.Handler for the patch settings name operation +func NewPatchSettingsName(ctx *middleware.Context, handler PatchSettingsNameHandler) *PatchSettingsName { + return &PatchSettingsName{Context: ctx, Handler: handler} +} + +/* + PatchSettingsName swagger:route PATCH /settings/{name} setting patchSettingsName + +Changed setting +*/ +type PatchSettingsName struct { + Context *middleware.Context + Handler PatchSettingsNameHandler +} + +func (o *PatchSettingsName) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPatchSettingsNameParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/setting/patch_settings_name_parameters.go b/console/service/restapi/operations/setting/patch_settings_name_parameters.go new file mode 100644 index 000000000..1d11909d1 --- /dev/null +++ b/console/service/restapi/operations/setting/patch_settings_name_parameters.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPatchSettingsNameParams creates a new PatchSettingsNameParams object +// +// There are no default values defined in the spec. +func NewPatchSettingsNameParams() PatchSettingsNameParams { + + return PatchSettingsNameParams{} +} + +// PatchSettingsNameParams contains all the bound params for the patch settings name operation +// typically these are obtained from a http.Request +// +// swagger:parameters PatchSettingsName +type PatchSettingsNameParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestChangeSetting + /* + Required: true + In: path + */ + Name string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPatchSettingsNameParams() beforehand. +func (o *PatchSettingsNameParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestChangeSetting + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + + rName, rhkName, _ := route.Params.GetOK("name") + if err := o.bindName(rName, rhkName, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindName binds and validates parameter Name from path. +func (o *PatchSettingsNameParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.Name = raw + + return nil +} diff --git a/console/service/restapi/operations/setting/patch_settings_name_responses.go b/console/service/restapi/operations/setting/patch_settings_name_responses.go new file mode 100644 index 000000000..adc848e27 --- /dev/null +++ b/console/service/restapi/operations/setting/patch_settings_name_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PatchSettingsNameOKCode is the HTTP code returned for type PatchSettingsNameOK +const PatchSettingsNameOKCode int = 200 + +/* +PatchSettingsNameOK OK + +swagger:response patchSettingsNameOK +*/ +type PatchSettingsNameOK struct { + + /* + In: Body + */ + Payload *models.ResponseSetting `json:"body,omitempty"` +} + +// NewPatchSettingsNameOK creates PatchSettingsNameOK with default headers values +func NewPatchSettingsNameOK() *PatchSettingsNameOK { + + return &PatchSettingsNameOK{} +} + +// WithPayload adds the payload to the patch settings name o k response +func (o *PatchSettingsNameOK) WithPayload(payload *models.ResponseSetting) *PatchSettingsNameOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the patch settings name o k response +func (o *PatchSettingsNameOK) SetPayload(payload *models.ResponseSetting) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PatchSettingsNameOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PatchSettingsNameBadRequestCode is the HTTP code returned for type PatchSettingsNameBadRequest +const PatchSettingsNameBadRequestCode int = 400 + +/* +PatchSettingsNameBadRequest Error + +swagger:response patchSettingsNameBadRequest +*/ +type PatchSettingsNameBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPatchSettingsNameBadRequest creates PatchSettingsNameBadRequest with default headers values +func NewPatchSettingsNameBadRequest() *PatchSettingsNameBadRequest { + + return &PatchSettingsNameBadRequest{} +} + +// WithPayload adds the payload to the patch settings name bad request response +func (o *PatchSettingsNameBadRequest) WithPayload(payload *models.ResponseError) *PatchSettingsNameBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the patch settings name bad request response +func (o *PatchSettingsNameBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PatchSettingsNameBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/setting/patch_settings_name_urlbuilder.go b/console/service/restapi/operations/setting/patch_settings_name_urlbuilder.go new file mode 100644 index 000000000..c41850cf6 --- /dev/null +++ b/console/service/restapi/operations/setting/patch_settings_name_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// PatchSettingsNameURL generates an URL for the patch settings name operation +type PatchSettingsNameURL struct { + Name string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PatchSettingsNameURL) WithBasePath(bp string) *PatchSettingsNameURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PatchSettingsNameURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PatchSettingsNameURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/settings/{name}" + + name := o.Name + if name != "" { + _path = strings.Replace(_path, "{name}", name, -1) + } else { + return nil, errors.New("name is required on PatchSettingsNameURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PatchSettingsNameURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PatchSettingsNameURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PatchSettingsNameURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PatchSettingsNameURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PatchSettingsNameURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PatchSettingsNameURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/setting/post_settings.go b/console/service/restapi/operations/setting/post_settings.go new file mode 100644 index 000000000..2fa8623f5 --- /dev/null +++ b/console/service/restapi/operations/setting/post_settings.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// PostSettingsHandlerFunc turns a function with the right signature into a post settings handler +type PostSettingsHandlerFunc func(PostSettingsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn PostSettingsHandlerFunc) Handle(params PostSettingsParams) middleware.Responder { + return fn(params) +} + +// PostSettingsHandler interface for that can handle valid post settings params +type PostSettingsHandler interface { + Handle(PostSettingsParams) middleware.Responder +} + +// NewPostSettings creates a new http.Handler for the post settings operation +func NewPostSettings(ctx *middleware.Context, handler PostSettingsHandler) *PostSettings { + return &PostSettings{Context: ctx, Handler: handler} +} + +/* + PostSettings swagger:route POST /settings setting postSettings + +Create new setting +*/ +type PostSettings struct { + Context *middleware.Context + Handler PostSettingsHandler +} + +func (o *PostSettings) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewPostSettingsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/setting/post_settings_parameters.go b/console/service/restapi/operations/setting/post_settings_parameters.go new file mode 100644 index 000000000..77a64c700 --- /dev/null +++ b/console/service/restapi/operations/setting/post_settings_parameters.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "postgesql-cluster-console/models" +) + +// NewPostSettingsParams creates a new PostSettingsParams object +// +// There are no default values defined in the spec. +func NewPostSettingsParams() PostSettingsParams { + + return PostSettingsParams{} +} + +// PostSettingsParams contains all the bound params for the post settings operation +// typically these are obtained from a http.Request +// +// swagger:parameters PostSettings +type PostSettingsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *models.RequestCreateSetting +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewPostSettingsParams() beforehand. +func (o *PostSettingsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.RequestCreateSetting + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/setting/post_settings_responses.go b/console/service/restapi/operations/setting/post_settings_responses.go new file mode 100644 index 000000000..0205ab5e9 --- /dev/null +++ b/console/service/restapi/operations/setting/post_settings_responses.go @@ -0,0 +1,104 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// PostSettingsOKCode is the HTTP code returned for type PostSettingsOK +const PostSettingsOKCode int = 200 + +/* +PostSettingsOK OK + +swagger:response postSettingsOK +*/ +type PostSettingsOK struct { + + /* + In: Body + */ + Payload *models.ResponseSetting `json:"body,omitempty"` +} + +// NewPostSettingsOK creates PostSettingsOK with default headers values +func NewPostSettingsOK() *PostSettingsOK { + + return &PostSettingsOK{} +} + +// WithPayload adds the payload to the post settings o k response +func (o *PostSettingsOK) WithPayload(payload *models.ResponseSetting) *PostSettingsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post settings o k response +func (o *PostSettingsOK) SetPayload(payload *models.ResponseSetting) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostSettingsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// PostSettingsBadRequestCode is the HTTP code returned for type PostSettingsBadRequest +const PostSettingsBadRequestCode int = 400 + +/* +PostSettingsBadRequest Error + +swagger:response postSettingsBadRequest +*/ +type PostSettingsBadRequest struct { + + /* + In: Body + */ + Payload *models.ResponseError `json:"body,omitempty"` +} + +// NewPostSettingsBadRequest creates PostSettingsBadRequest with default headers values +func NewPostSettingsBadRequest() *PostSettingsBadRequest { + + return &PostSettingsBadRequest{} +} + +// WithPayload adds the payload to the post settings bad request response +func (o *PostSettingsBadRequest) WithPayload(payload *models.ResponseError) *PostSettingsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the post settings bad request response +func (o *PostSettingsBadRequest) SetPayload(payload *models.ResponseError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *PostSettingsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/setting/post_settings_urlbuilder.go b/console/service/restapi/operations/setting/post_settings_urlbuilder.go new file mode 100644 index 000000000..8dd905ee3 --- /dev/null +++ b/console/service/restapi/operations/setting/post_settings_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package setting + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// PostSettingsURL generates an URL for the post settings operation +type PostSettingsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostSettingsURL) WithBasePath(bp string) *PostSettingsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *PostSettingsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *PostSettingsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/settings" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *PostSettingsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *PostSettingsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *PostSettingsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on PostSettingsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on PostSettingsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *PostSettingsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/operations/system/get_version.go b/console/service/restapi/operations/system/get_version.go new file mode 100644 index 000000000..aa59b4d70 --- /dev/null +++ b/console/service/restapi/operations/system/get_version.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package system + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetVersionHandlerFunc turns a function with the right signature into a get version handler +type GetVersionHandlerFunc func(GetVersionParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetVersionHandlerFunc) Handle(params GetVersionParams) middleware.Responder { + return fn(params) +} + +// GetVersionHandler interface for that can handle valid get version params +type GetVersionHandler interface { + Handle(GetVersionParams) middleware.Responder +} + +// NewGetVersion creates a new http.Handler for the get version operation +func NewGetVersion(ctx *middleware.Context, handler GetVersionHandler) *GetVersion { + return &GetVersion{Context: ctx, Handler: handler} +} + +/* + GetVersion swagger:route GET /version system getVersion + +Get version of server +*/ +type GetVersion struct { + Context *middleware.Context + Handler GetVersionHandler +} + +func (o *GetVersion) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetVersionParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/console/service/restapi/operations/system/get_version_parameters.go b/console/service/restapi/operations/system/get_version_parameters.go new file mode 100644 index 000000000..9f2ef3147 --- /dev/null +++ b/console/service/restapi/operations/system/get_version_parameters.go @@ -0,0 +1,46 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package system + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" +) + +// NewGetVersionParams creates a new GetVersionParams object +// +// There are no default values defined in the spec. +func NewGetVersionParams() GetVersionParams { + + return GetVersionParams{} +} + +// GetVersionParams contains all the bound params for the get version operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetVersion +type GetVersionParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetVersionParams() beforehand. +func (o *GetVersionParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/console/service/restapi/operations/system/get_version_responses.go b/console/service/restapi/operations/system/get_version_responses.go new file mode 100644 index 000000000..0f0f5f91e --- /dev/null +++ b/console/service/restapi/operations/system/get_version_responses.go @@ -0,0 +1,59 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package system + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "postgesql-cluster-console/models" +) + +// GetVersionOKCode is the HTTP code returned for type GetVersionOK +const GetVersionOKCode int = 200 + +/* +GetVersionOK OK + +swagger:response getVersionOK +*/ +type GetVersionOK struct { + + /* + In: Body + */ + Payload *models.ResponseVersion `json:"body,omitempty"` +} + +// NewGetVersionOK creates GetVersionOK with default headers values +func NewGetVersionOK() *GetVersionOK { + + return &GetVersionOK{} +} + +// WithPayload adds the payload to the get version o k response +func (o *GetVersionOK) WithPayload(payload *models.ResponseVersion) *GetVersionOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get version o k response +func (o *GetVersionOK) SetPayload(payload *models.ResponseVersion) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetVersionOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/console/service/restapi/operations/system/get_version_urlbuilder.go b/console/service/restapi/operations/system/get_version_urlbuilder.go new file mode 100644 index 000000000..3b5a18a31 --- /dev/null +++ b/console/service/restapi/operations/system/get_version_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package system + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// GetVersionURL generates an URL for the get version operation +type GetVersionURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetVersionURL) WithBasePath(bp string) *GetVersionURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetVersionURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetVersionURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/version" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetVersionURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetVersionURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetVersionURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetVersionURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetVersionURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetVersionURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/console/service/restapi/server.go b/console/service/restapi/server.go new file mode 100644 index 000000000..f751f2289 --- /dev/null +++ b/console/service/restapi/server.go @@ -0,0 +1,508 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package restapi + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "os/signal" + "strconv" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/go-openapi/runtime/flagext" + "github.com/go-openapi/swag" + flags "github.com/jessevdk/go-flags" + "golang.org/x/net/netutil" + + "postgesql-cluster-console/restapi/operations" +) + +const ( + schemeHTTP = "http" + schemeHTTPS = "https" + schemeUnix = "unix" +) + +var defaultSchemes []string + +func init() { + defaultSchemes = []string{ + schemeHTTP, + } +} + +// NewServer creates a new api pg console server but does not configure it +func NewServer(api *operations.PgConsoleAPI) *Server { + s := new(Server) + + s.shutdown = make(chan struct{}) + s.api = api + s.interrupt = make(chan os.Signal, 1) + return s +} + +// ConfigureAPI configures the API and handlers. +func (s *Server) ConfigureAPI() { + if s.api != nil { + s.handler = configureAPI(s.api) + } +} + +// ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse +func (s *Server) ConfigureFlags() { + if s.api != nil { + configureFlags(s.api) + } +} + +// Server for the pg console API +type Server struct { + EnabledListeners []string `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"` + CleanupTimeout time.Duration `long:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"` + GracefulTimeout time.Duration `long:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"` + MaxHeaderSize flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"` + + SocketPath flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/pg-console.sock"` + domainSocketL net.Listener + + Host string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"` + Port int `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"` + ListenLimit int `long:"listen-limit" description:"limit the number of outstanding requests"` + KeepAlive time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"` + ReadTimeout time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"` + WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"` + httpServerL net.Listener + + TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"` + TLSPort int `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"` + TLSCertificate flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"` + TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"` + TLSCACertificate flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"` + TLSListenLimit int `long:"tls-listen-limit" description:"limit the number of outstanding requests"` + TLSKeepAlive time.Duration `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"` + TLSReadTimeout time.Duration `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"` + TLSWriteTimeout time.Duration `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"` + httpsServerL net.Listener + + api *operations.PgConsoleAPI + handler http.Handler + hasListeners bool + shutdown chan struct{} + shuttingDown int32 + interrupted bool + interrupt chan os.Signal +} + +// Logf logs message either via defined user logger or via system one if no user logger is defined. +func (s *Server) Logf(f string, args ...interface{}) { + if s.api != nil && s.api.Logger != nil { + s.api.Logger(f, args...) + } else { + log.Printf(f, args...) + } +} + +// Fatalf logs message either via defined user logger or via system one if no user logger is defined. +// Exits with non-zero status after printing +func (s *Server) Fatalf(f string, args ...interface{}) { + if s.api != nil && s.api.Logger != nil { + s.api.Logger(f, args...) + os.Exit(1) + } else { + log.Fatalf(f, args...) + } +} + +// SetAPI configures the server with the specified API. Needs to be called before Serve +func (s *Server) SetAPI(api *operations.PgConsoleAPI) { + if api == nil { + s.api = nil + s.handler = nil + return + } + + s.api = api + s.handler = configureAPI(api) +} + +func (s *Server) hasScheme(scheme string) bool { + schemes := s.EnabledListeners + if len(schemes) == 0 { + schemes = defaultSchemes + } + + for _, v := range schemes { + if v == scheme { + return true + } + } + return false +} + +// Serve the api +func (s *Server) Serve() (err error) { + if !s.hasListeners { + if err = s.Listen(); err != nil { + return err + } + } + + // set default handler, if none is set + if s.handler == nil { + if s.api == nil { + return errors.New("can't create the default handler, as no api is set") + } + + s.SetHandler(s.api.Serve(nil)) + } + + wg := new(sync.WaitGroup) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s) + + servers := []*http.Server{} + + if s.hasScheme(schemeUnix) { + domainSocket := new(http.Server) + domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) + domainSocket.Handler = s.handler + if int64(s.CleanupTimeout) > 0 { + domainSocket.IdleTimeout = s.CleanupTimeout + } + + configureServer(domainSocket, "unix", string(s.SocketPath)) + + servers = append(servers, domainSocket) + wg.Add(1) + s.Logf("Serving pg console at unix://%s", s.SocketPath) + go func(l net.Listener) { + defer wg.Done() + if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed { + s.Fatalf("%v", err) + } + s.Logf("Stopped serving pg console at unix://%s", s.SocketPath) + }(s.domainSocketL) + } + + if s.hasScheme(schemeHTTP) { + httpServer := new(http.Server) + httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) + httpServer.ReadTimeout = s.ReadTimeout + httpServer.WriteTimeout = s.WriteTimeout + httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) + if s.ListenLimit > 0 { + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) + } + + if int64(s.CleanupTimeout) > 0 { + httpServer.IdleTimeout = s.CleanupTimeout + } + + httpServer.Handler = s.handler + + configureServer(httpServer, "http", s.httpServerL.Addr().String()) + + servers = append(servers, httpServer) + wg.Add(1) + s.Logf("Serving pg console at http://%s", s.httpServerL.Addr()) + go func(l net.Listener) { + defer wg.Done() + if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed { + s.Fatalf("%v", err) + } + s.Logf("Stopped serving pg console at http://%s", l.Addr()) + }(s.httpServerL) + } + + if s.hasScheme(schemeHTTPS) { + httpsServer := new(http.Server) + httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) + httpsServer.ReadTimeout = s.TLSReadTimeout + httpsServer.WriteTimeout = s.TLSWriteTimeout + httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) + if s.TLSListenLimit > 0 { + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) + } + if int64(s.CleanupTimeout) > 0 { + httpsServer.IdleTimeout = s.CleanupTimeout + } + httpsServer.Handler = s.handler + + // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go + httpsServer.TLSConfig = &tls.Config{ + // Causes servers to use Go's default ciphersuite preferences, + // which are tuned to avoid attacks. Does nothing on clients. + PreferServerCipherSuites: true, + // Only use curves which have assembly implementations + // https://github.com/golang/go/tree/master/src/crypto/elliptic + CurvePreferences: []tls.CurveID{tls.CurveP256}, + // Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility + NextProtos: []string{"h2", "http/1.1"}, + // https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols + MinVersion: tls.VersionTLS12, + // These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + }, + } + + // build standard config from server options + if s.TLSCertificate != "" && s.TLSCertificateKey != "" { + httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1) + httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey)) + if err != nil { + return err + } + } + + if s.TLSCACertificate != "" { + // include specified CA certificate + caCert, caCertErr := ioutil.ReadFile(string(s.TLSCACertificate)) + if caCertErr != nil { + return caCertErr + } + caCertPool := x509.NewCertPool() + ok := caCertPool.AppendCertsFromPEM(caCert) + if !ok { + return fmt.Errorf("cannot parse CA certificate") + } + httpsServer.TLSConfig.ClientCAs = caCertPool + httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + + // call custom TLS configurator + configureTLS(httpsServer.TLSConfig) + + if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil { + // after standard and custom config are passed, this ends up with no certificate + if s.TLSCertificate == "" { + if s.TLSCertificateKey == "" { + s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified") + } + s.Fatalf("the required flag `--tls-certificate` was not specified") + } + if s.TLSCertificateKey == "" { + s.Fatalf("the required flag `--tls-key` was not specified") + } + // this happens with a wrong custom TLS configurator + s.Fatalf("no certificate was configured for TLS") + } + + configureServer(httpsServer, "https", s.httpsServerL.Addr().String()) + + servers = append(servers, httpsServer) + wg.Add(1) + s.Logf("Serving pg console at https://%s", s.httpsServerL.Addr()) + go func(l net.Listener) { + defer wg.Done() + if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed { + s.Fatalf("%v", err) + } + s.Logf("Stopped serving pg console at https://%s", l.Addr()) + }(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig)) + } + + wg.Add(1) + go s.handleShutdown(wg, &servers) + + wg.Wait() + return nil +} + +// Listen creates the listeners for the server +func (s *Server) Listen() error { + if s.hasListeners { // already done this + return nil + } + + if s.hasScheme(schemeHTTPS) { + // Use http host if https host wasn't defined + if s.TLSHost == "" { + s.TLSHost = s.Host + } + // Use http listen limit if https listen limit wasn't defined + if s.TLSListenLimit == 0 { + s.TLSListenLimit = s.ListenLimit + } + // Use http tcp keep alive if https tcp keep alive wasn't defined + if int64(s.TLSKeepAlive) == 0 { + s.TLSKeepAlive = s.KeepAlive + } + // Use http read timeout if https read timeout wasn't defined + if int64(s.TLSReadTimeout) == 0 { + s.TLSReadTimeout = s.ReadTimeout + } + // Use http write timeout if https write timeout wasn't defined + if int64(s.TLSWriteTimeout) == 0 { + s.TLSWriteTimeout = s.WriteTimeout + } + } + + if s.hasScheme(schemeUnix) { + domSockListener, err := net.Listen("unix", string(s.SocketPath)) + if err != nil { + return err + } + s.domainSocketL = domSockListener + } + + if s.hasScheme(schemeHTTP) { + listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port))) + if err != nil { + return err + } + + h, p, err := swag.SplitHostPort(listener.Addr().String()) + if err != nil { + return err + } + s.Host = h + s.Port = p + s.httpServerL = listener + } + + if s.hasScheme(schemeHTTPS) { + tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort))) + if err != nil { + return err + } + + sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String()) + if err != nil { + return err + } + s.TLSHost = sh + s.TLSPort = sp + s.httpsServerL = tlsListener + } + + s.hasListeners = true + return nil +} + +// Shutdown server and clean up resources +func (s *Server) Shutdown() error { + if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) { + close(s.shutdown) + } + return nil +} + +func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) { + // wg.Done must occur last, after s.api.ServerShutdown() + // (to preserve old behaviour) + defer wg.Done() + + <-s.shutdown + + servers := *serversPtr + + ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout) + defer cancel() + + // first execute the pre-shutdown hook + s.api.PreServerShutdown() + + shutdownChan := make(chan bool) + for i := range servers { + server := servers[i] + go func() { + var success bool + defer func() { + shutdownChan <- success + }() + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + success = true + } + }() + } + + // Wait until all listeners have successfully shut down before calling ServerShutdown + success := true + for range servers { + success = success && <-shutdownChan + } + if success { + s.api.ServerShutdown() + } +} + +// GetHandler returns a handler useful for testing +func (s *Server) GetHandler() http.Handler { + return s.handler +} + +// SetHandler allows for setting a http handler on this server +func (s *Server) SetHandler(handler http.Handler) { + s.handler = handler +} + +// UnixListener returns the domain socket listener +func (s *Server) UnixListener() (net.Listener, error) { + if !s.hasListeners { + if err := s.Listen(); err != nil { + return nil, err + } + } + return s.domainSocketL, nil +} + +// HTTPListener returns the http listener +func (s *Server) HTTPListener() (net.Listener, error) { + if !s.hasListeners { + if err := s.Listen(); err != nil { + return nil, err + } + } + return s.httpServerL, nil +} + +// TLSListener returns the https listener +func (s *Server) TLSListener() (net.Listener, error) { + if !s.hasListeners { + if err := s.Listen(); err != nil { + return nil, err + } + } + return s.httpsServerL, nil +} + +func handleInterrupt(once *sync.Once, s *Server) { + once.Do(func() { + for range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + if err := s.Shutdown(); err != nil { + s.Logf("HTTP server Shutdown: %v", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/console/ui/.env b/console/ui/.env new file mode 100644 index 000000000..f89b2563f --- /dev/null +++ b/console/ui/.env @@ -0,0 +1,6 @@ +VITE_API_URL=http://localhost:8080/api/v1/ +VITE_AUTH_TOKEN=auth_token +VITE_CLUSTERS_POLLING_INTERVAL=60000 +VITE_CLUSTER_OVERVIEW_POLLING_INTERVAL=60000 +VITE_OPERATIONS_POLLING_INTERVAL=60000 +VITE_OPERATION_LOGS_POLLING_INTERVAL=10000 \ No newline at end of file diff --git a/console/ui/.env.production b/console/ui/.env.production new file mode 100644 index 000000000..78376365b --- /dev/null +++ b/console/ui/.env.production @@ -0,0 +1,6 @@ +VITE_API_URL=REPLACE_ME_WITH_API_URL +VITE_AUTH_TOKEN=REPLACE_ME_WITH_AUTH_TOKEN +VITE_CLUSTERS_POLLING_INTERVAL=REPLACE_ME_WITH_CLUSTERS_POLLING_INTERVAL +VITE_CLUSTER_OVERVIEW_POLLING_INTERVAL=REPLACE_ME_WITH_CLUSTER_OVERVIEW_POLLING_INTERVAL +VITE_OPERATIONS_POLLING_INTERVAL=REPLACE_ME_WITH_OPERATIONS_POLLING_INTERVAL +VITE_OPERATION_LOGS_POLLING_INTERVAL=REPLACE_ME_WITH_OPERATION_LOGS_POLLING_INTERVAL \ No newline at end of file diff --git a/console/ui/.eslintrc.cjs b/console/ui/.eslintrc.cjs new file mode 100644 index 000000000..ca2a2f7c8 --- /dev/null +++ b/console/ui/.eslintrc.cjs @@ -0,0 +1,28 @@ +module.exports = { + root: true, + env: {browser: true, es2020: true}, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended-type-checked', + 'plugin:react-hooks/recommended', + 'plugin:@typescript-eslint/stylistic-type-checked', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime' + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + {allowConstantExport: true}, + ], + '@typescript-eslint/no-misused-promises': 'off' + }, +} diff --git a/console/ui/.gitignore b/console/ui/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/console/ui/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/console/ui/.prettierignore b/console/ui/.prettierignore new file mode 100644 index 000000000..c2ed210f5 --- /dev/null +++ b/console/ui/.prettierignore @@ -0,0 +1,6 @@ +build/ +dist/ +node_modules/ +public/ +package.json +yarn.lock diff --git a/console/ui/.prettierrc b/console/ui/.prettierrc new file mode 100644 index 000000000..e47db5689 --- /dev/null +++ b/console/ui/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "bracketSameLine": true, + "arrowParens": "always", + "endOfLine": "auto", + "bracketSpacing": true +} diff --git a/console/ui/Dockerfile b/console/ui/Dockerfile new file mode 100644 index 000000000..34d52bda9 --- /dev/null +++ b/console/ui/Dockerfile @@ -0,0 +1,24 @@ +FROM node:20-alpine as build + +WORKDIR /usr/src/${PROJECT_NAME} + +COPY . ./ + +RUN yarn install --frozen-lockfile --network-timeout 1000000 + +RUN yarn vite build + +FROM registry.gs-labs.tv/casdevops/base_images:nginx1.21-alpine as runtime +ARG PROJECT_NAME + +COPY nginx/nginx.conf /etc/nginx/ + +WORKDIR /usr/share/nginx/html +COPY --from=build /usr/src/${PROJECT_NAME}/dist ./ +COPY env.sh .env ./ + +RUN apk update && apk add bash && \ + chmod +x env.sh + +CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""] + diff --git a/console/ui/README.md b/console/ui/README.md new file mode 100644 index 000000000..707720761 --- /dev/null +++ b/console/ui/README.md @@ -0,0 +1,140 @@ +# postgres-cluster-console-ui + +UI part of **postgresql-cluster-console**. This project provides a user-friendly web interface for managing, +monitoring, and configuring database clusters. + +## Table of Contents + +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [Technology Stack](#technology-stack) +- [Configuration](#configuration) +- [Architecture](#architecture) +- [Contact](#contact) + +## Features + +- **Cluster management**: Add, remove, and monitor cloud provider clusters as well as clusters on your own machines. +- **Cluster overview**: View general information and status of cluster instances. +- **Operations**: View cluster operations and deployment logs. +- **Projects**: Deploy multiple projects with different clusters. +- **Settings**: Use proxy servers to deploy clusters. +- **Credential control**: Easily manage multiple credentials for different cloud providers and your own machines. + +## Installation + +To run this project locally, follow these steps: + +1. **Clone repository** + +``` +git clone https://github.com/vitabaks/postgresql_cluster.git cd postgresql_cluster/console/ui +``` + +2. **Install dependencies** + +```yarn install``` + +3. **Start development server** + +```yarn run dev``` + +## Usage + +### Running the App in Development Mode + +1. Ensure you have installed all dependencies with ```yarn install```. +2. Start the development server with ```yarn run dev```. +3. Browser with app should open automatically. If this didn't happen open your browser and navigate + to http://localhost:5173. + +### Building for Production + +To create a production build: + +```yarn run build``` + +The optimized build will be output to the `dist` folder. You can then serve this with any static server. + +## Technology Stack + +**UI:** + +- React +- Redux Toolkit (RTK Query for data fetching) +- React Router v6 +- Vite (development and build tool) +- Material UI (UI kit) +- Material React Table V2 +- React-toastify + +**Deployment:** + +- Docker (included Dockerfile for quick deployment) +- Nginx (project configuration included) + +## Configuration + +There are several env variables that configure UI: + +| KEY | DEFAULT | DESCRIPTION | +|----------------------------------------------|------------------------------|-------------------------------------------------------------| +| PG_CONSOLE_API_URL | http://localhost:8080/api/v1 | Default API URL where frontend will be sending requests to. | +| PG_CONSOLE_AUTHORIZATION_TOKEN | auth_token | Reference auth token that will be used for login. | +| PG_CONSOLE_CLUSTERS_POLLING_INTERVAL | 60000 | Clusters table refresh interval in seconds. | +| PG_CONSOLE_CLUSTER_OVERVIEW_POLLING_INTERVAL | 60000 | Cluster overview refresh interval in seconds. | +| PG_CONSOLE_OPERATIONS_POLLING_INTERVAL | 60000 | Operations table refresh interval in seconds. | +| PG_CONSOLE_OPERATION_LOGS_POLLING_INTERVAL | 10000 | Operation logs refresh interval in seconds. | + +## Architecture + +UI uses [Feature-Sliced Design](https://feature-sliced.design/) v2 approach to implement architecture. +This design pattern divides the application into distinct layers and slices, each with a specific role and +responsibility, to promote isolation, reusability, and easy maintenance. + +### Feature-Sliced Design Overview + +#### Layers + +1. **App Layer** + +- Description: This is the top-level layer, responsible for initializing the application, setting up providers (like + routers, states, etc.), and global styles. +- Contents: +- App: Main application component that integrates all providers and initializes the app. +- providers: Context providers such as Redux Provider, Router, Theme, etc. +- styles: Global styles and theming. + +2. **Pages Layer** + +- Description: Represents the application screens or pages. Each page can consist of multiple features and/or entities. +- Contents: Page components like AddCluster, Login, 404, etc. + +3. **Features Layer** + +- Description: This layer contains interactive components such as buttons, modals, etc. +- Contents: Feature components like AddSecret, LogoutButton, OperationsTableRowActions, etc. + +4. **Entities Layer** + +- Description: Contains core business entities of the application. Additionally, reusable form parts are also made + entities. +- Contents: Entities like SidebarItem, SecretFormBlock, etc. + +5. **Shared Layer** + +- Description: This is the foundational layer. It includes utilities, shared components, constants, and other reusable + elements that can be used across features, entities, or pages. +- Contents: Common components (CopyIcon, DefaultTable, Spinner), constants and types, utility functions. + +## Contact + +For additional questions, feedback, or issues, you can reach us at: + +- GitHub Issues: Issue Tracker (https://github.com/vitabaks/postgresql_cluster/issues) + +--- + +Thank you for using and contributing to **postgres-cluster-console**! Your support helps make this project +better. \ No newline at end of file diff --git a/console/ui/env.sh b/console/ui/env.sh new file mode 100644 index 000000000..494f457d3 --- /dev/null +++ b/console/ui/env.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +SEARCH_DIR="/usr/share/nginx/html/" + +# Set default values for environment variables if they are not set +export VITE_API_URL=${VITE_API_URL:-${PG_CONSOLE_API_URL:-"http://localhost:8080/api/v1"}} +export VITE_AUTH_TOKEN=${VITE_AUTH_TOKEN:-${PG_CONSOLE_AUTHORIZATION_TOKEN:-"auth_token"}} +export VITE_CLUSTERS_POLLING_INTERVAL=${PG_CONSOLE_CLUSTERS_POLLING_INTERVAL:-"60000"} +export VITE_CLUSTER_OVERVIEW_POLLING_INTERVAL=${PG_CONSOLE_CLUSTER_OVERVIEW_POLLING_INTERVAL:-"60000"} +export VITE_OPERATIONS_POLLING_INTERVAL=${PG_CONSOLE_OPERATIONS_POLLING_INTERVAL:-"60000"} +export VITE_OPERATION_LOGS_POLLING_INTERVAL=${PG_CONSOLE_OPERATION_LOGS_POLLING_INTERVAL:-"10000"} + +# Find all .js files in the specified directory and replace placeholders with the environment variable values +find "${SEARCH_DIR}" -type f -name '*.js' -exec sed -i -e " + s|REPLACE_ME_WITH_API_URL|${VITE_API_URL}|g; + s|REPLACE_ME_WITH_AUTH_TOKEN|${VITE_AUTH_TOKEN}|g; + s|REPLACE_ME_WITH_CLUSTERS_POLLING_INTERVAL|${VITE_CLUSTERS_POLLING_INTERVAL}|g; + s|REPLACE_ME_WITH_CLUSTER_OVERVIEW_POLLING_INTERVAL|${VITE_CLUSTER_OVERVIEW_POLLING_INTERVAL}|g; + s|REPLACE_ME_WITH_OPERATIONS_POLLING_INTERVAL|${VITE_OPERATIONS_POLLING_INTERVAL}|g; + s|REPLACE_ME_WITH_OPERATION_LOGS_POLLING_INTERVAL|${VITE_OPERATION_LOGS_POLLING_INTERVAL}|g; +" {} \; diff --git a/console/ui/index.html b/console/ui/index.html new file mode 100644 index 000000000..c53d9a151 --- /dev/null +++ b/console/ui/index.html @@ -0,0 +1,13 @@ + + + + + + + PostgreSQL Cluster Console + + +
+ + + diff --git a/console/ui/nginx/nginx.conf b/console/ui/nginx/nginx.conf new file mode 100644 index 000000000..7f2a9fb30 --- /dev/null +++ b/console/ui/nginx/nginx.conf @@ -0,0 +1,48 @@ +worker_processes auto; + +events { + worker_connections 8000; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PATCH, DELETE, PUT' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers,X-Auth-Token' always; + + listen 80; + access_log /var/log/nginx/access.log; + + root /usr/share/nginx/html; + index index.html index.htm; + + location / { + try_files $uri $uri/ /index.html; + sendfile off; + add_header Last-Modified $date_gmt; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + if_modified_since off; + expires off; + etag off; + proxy_no_cache 1; + proxy_cache_bypass 1; + } + + location ~* \.(?:css|js)$ { + try_files $uri =404; + expires 1y; + access_log off; + add_header Cache-Control "public"; + } + + location ~ ^.+\..+$ { + try_files $uri =404; + } + } +} + diff --git a/console/ui/package.json b/console/ui/package.json new file mode 100644 index 000000000..4a67b3f4d --- /dev/null +++ b/console/ui/package.json @@ -0,0 +1,77 @@ +{ + "name": "postgres-cluster-console-ui", + "private": true, + "version": "0.0.47", + "type": "module", + "scripts": { + "dev": "vite --open", + "build": "vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "tsc && vite build && vite preview", + "serve": "vite build && serve ./dist", + "apiGen": "npx @rtk-query/codegen-openapi src/shared/api/apiConfig.ts", + "vitest": "vitest run", + "vitest:watch": "vitest" + }, + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fontsource/roboto": "^5.0.13", + "@hookform/resolvers": "^3.4.2", + "@monaco-editor/react": "^4.6.0", + "@mui/icons-material": "^5.15.17", + "@mui/lab": "^5.0.0-alpha.170", + "@mui/material": "^5.15.17", + "@mui/x-data-grid": "^7.4.0", + "@mui/x-date-pickers": "^7.5.0", + "@reduxjs/toolkit": "^2.2.6", + "date-fns": "^3.6.0", + "i18next": "^23.11.3", + "i18next-browser-languagedetector": "^7.2.1", + "i18next-fs-backend": "^2.3.1", + "i18next-http-backend": "^2.5.1", + "ip-regex": "^5.0.0", + "material-react-table": "^2.13.0", + "normalize.css": "^8.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.13", + "react-hook-form": "^7.51.5", + "react-i18next": "^14.1.1", + "react-lazylog": "^4.5.3", + "react-redux": "^9.1.2", + "react-router-dom": "^6.23.0", + "react-toastify": "^10.0.5", + "yup": "^1.4.0" + }, + "devDependencies": { + "@faker-js/faker": "^8.4.1", + "@rtk-query/codegen-openapi": "^1.2.0", + "@testing-library/dom": "^10.1.0", + "@testing-library/jest-dom": "^6.4.5", + "@testing-library/react": "^15.0.6", + "@testing-library/user-event": "^14.5.2", + "@types/node": "^20.12.10", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@types/react-lazylog": "^4.5.4", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "@vitejs/plugin-react-swc": "^3.6.0", + "autoprefixer": "^10.4.19", + "esbuild-plugin-react-virtualized": "^1.0.4", + "esbuild-runner": "^2.2.2", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "jsdom": "^24.0.0", + "prettier": "^3.2.5", + "sass": "^1.76.0", + "typescript": "^5.2.2", + "vite": "^5.2.0", + "vite-plugin-svgr": "^4.2.0", + "vitest": "^1.6.0" + } +} diff --git a/console/ui/src/app/App.tsx b/console/ui/src/app/App.tsx new file mode 100644 index 000000000..3683722fb --- /dev/null +++ b/console/ui/src/app/App.tsx @@ -0,0 +1,24 @@ +import { FC } from 'react'; +import { ThemeProvider } from '@mui/material'; +import theme from '@shared/theme/theme.ts'; +import Router from '@app/router/Router.tsx'; +import { Provider } from 'react-redux'; +import { ToastContainer } from 'react-toastify'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; +import { store } from '@app/redux/store/store.ts'; + +const App: FC = () => { + return ( + + + + + + + + + ); +}; + +export default App; diff --git a/console/ui/src/app/layout/index.ts b/console/ui/src/app/layout/index.ts new file mode 100644 index 000000000..7c9056883 --- /dev/null +++ b/console/ui/src/app/layout/index.ts @@ -0,0 +1,3 @@ +import Layout from './ui'; + +export default Layout; diff --git a/console/ui/src/app/layout/ui/index.tsx b/console/ui/src/app/layout/ui/index.tsx new file mode 100644 index 000000000..e9fb3b737 --- /dev/null +++ b/console/ui/src/app/layout/ui/index.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import Sidebar from '@widgets/sidebar'; +import Header from '@widgets/header'; +import Main from '@widgets/main'; + +const Layout: FC = () => { + return ( +
+
+ +
+
+ ); +}; + +export default Layout; diff --git a/console/ui/src/app/main.tsx b/console/ui/src/app/main.tsx new file mode 100644 index 000000000..530783d4a --- /dev/null +++ b/console/ui/src/app/main.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App.tsx'; +import 'normalize.css/normalize.css'; +import '@shared/i18n/i18n.ts'; +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; +import 'react-toastify/dist/ReactToastify.min.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/console/ui/src/app/redux/slices/projectSlice/projectSelectors.ts b/console/ui/src/app/redux/slices/projectSlice/projectSelectors.ts new file mode 100644 index 000000000..0db80f572 --- /dev/null +++ b/console/ui/src/app/redux/slices/projectSlice/projectSelectors.ts @@ -0,0 +1,3 @@ +import { RootState } from '@app/redux/store/store.ts'; + +export const selectCurrentProject = (state: RootState) => state.project.currentProject; diff --git a/console/ui/src/app/redux/slices/projectSlice/projectSlice.ts b/console/ui/src/app/redux/slices/projectSlice/projectSlice.ts new file mode 100644 index 000000000..64ac1dfd9 --- /dev/null +++ b/console/ui/src/app/redux/slices/projectSlice/projectSlice.ts @@ -0,0 +1,24 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface ProjectSliceState { + currentProject: string | null; +} + +const initialState: ProjectSliceState = { + currentProject: localStorage.getItem('currentProject') ?? '', +}; + +export const projectSlice = createSlice({ + name: 'project', + initialState, + reducers: { + setProject: (state: ProjectSliceState, action: PayloadAction) => { + state.currentProject = action.payload; + localStorage.setItem('currentProject', action.payload); + }, + }, +}); + +export const { setProject } = projectSlice.actions; + +export default projectSlice.reducer; diff --git a/console/ui/src/app/redux/store/hooks.ts b/console/ui/src/app/redux/store/hooks.ts new file mode 100644 index 000000000..5cdf32436 --- /dev/null +++ b/console/ui/src/app/redux/store/hooks.ts @@ -0,0 +1,6 @@ +import { useDispatch, useSelector } from 'react-redux'; +import type { AppDispatch, RootState } from './store'; + +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); diff --git a/console/ui/src/app/redux/store/store.ts b/console/ui/src/app/redux/store/store.ts new file mode 100644 index 000000000..b80a2787b --- /dev/null +++ b/console/ui/src/app/redux/store/store.ts @@ -0,0 +1,71 @@ +import { Action, configureStore, isRejectedWithValue, Middleware, ThunkAction } from '@reduxjs/toolkit'; +import { operationsApi } from '@shared/api/api/operations'; +import { clustersApi } from '@shared/api/api/clusters.ts'; +import { environmentsApi } from '@shared/api/api/environments.ts'; +import { projectsApi } from '@shared/api/api/projects.ts'; +import { secretsApi } from '@shared/api/api/secrets.ts'; +import { settingsApi } from '@shared/api/api/settings.ts'; +import { otherApi } from '@shared/api/api/other.ts'; +import { projectSlice } from '@app/redux/slices/projectSlice/projectSlice.ts'; +import { baseApi } from '@shared/api/baseApi.ts'; +import { toast } from 'react-toastify'; +import { setupListeners } from '@reduxjs/toolkit/query'; + +export const rtkQueryErrorLogger: Middleware = () => (next) => (action) => { + if (isRejectedWithValue(action)) { + toast.error(action.payload?.data?.message || action.payload.data?.title); + console.error(action.payload?.data); + } + return next(action); +}; + +// `combineSlices` automatically combines the reducers using +// their `reducerPath`s, therefore we no longer need to call `combineReducers`. +const rootReducer = { + [baseApi.reducerPath]: baseApi.reducer, + [clustersApi.reducerPath]: clustersApi.reducer, + [environmentsApi.reducerPath]: environmentsApi.reducer, + [operationsApi.reducerPath]: operationsApi.reducer, + [projectsApi.reducerPath]: projectsApi.reducer, + [secretsApi.reducerPath]: secretsApi.reducer, + [settingsApi.reducerPath]: settingsApi.reducer, + [otherApi.reducerPath]: otherApi.reducer, + project: projectSlice.reducer, +}; + +// Infer the `RootState` type from the root reducer +export type RootState = ReturnType; + +export const makeStore = (preloadedState?: Partial) => { + const store = configureStore({ + reducer: rootReducer, + // Adding the api middleware enables caching, invalidation, polling, + // and other useful features of `rtk-query`. + middleware: (getDefaultMiddleware) => { + return getDefaultMiddleware().concat( + baseApi.middleware, + clustersApi.middleware, + environmentsApi.middleware, + operationsApi.middleware, + projectsApi.middleware, + secretsApi.middleware, + settingsApi.middleware, + otherApi.middleware, + rtkQueryErrorLogger, + ); + }, + preloadedState, + }); + // configure listeners using the provided defaults + // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors + setupListeners(store.dispatch); + return store; +}; + +export const store = makeStore(); + +// Infer the type of `store` +export type AppStore = typeof store; +// Infer the `AppDispatch` type from the store itself +export type AppDispatch = AppStore['dispatch']; +export type AppThunk = ThunkAction; diff --git a/console/ui/src/app/router/PrivateRouterWrapper.tsx b/console/ui/src/app/router/PrivateRouterWrapper.tsx new file mode 100644 index 000000000..0fd110c15 --- /dev/null +++ b/console/ui/src/app/router/PrivateRouterWrapper.tsx @@ -0,0 +1,23 @@ +import { Navigate, Outlet, useLocation } from 'react-router-dom'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { FC, useEffect } from 'react'; +import { toast } from 'react-toastify'; +import { useTranslation } from 'react-i18next'; +import { AUTH_TOKEN } from '@shared/config/constants.ts'; + +const PrivateRouteWrapper: FC = () => { + const { t } = useTranslation('toasts'); + const location = useLocation(); + + useEffect(() => { + if (localStorage.getItem('token') !== AUTH_TOKEN) toast.error(t('invalidToken')); + }, [localStorage.getItem('token')]); + + return localStorage.getItem('token') === AUTH_TOKEN ? ( + + ) : ( + + ); +}; + +export default PrivateRouteWrapper; diff --git a/console/ui/src/app/router/Router.tsx b/console/ui/src/app/router/Router.tsx new file mode 100644 index 000000000..4a78e298a --- /dev/null +++ b/console/ui/src/app/router/Router.tsx @@ -0,0 +1,49 @@ +import { FC, lazy, Suspense } from 'react'; +import { + createBrowserRouter, + createRoutesFromElements, + Navigate, + Outlet, + Route, + RouterProvider, +} from 'react-router-dom'; +import Layout from '../layout'; +import ClustersRoutes from '@app/router/routerConfig/ClustersRoutes.tsx'; +import OperationsRoutes from '@app/router/routerConfig/OperationsRoutes.tsx'; +import SettingsRoutes from '@app/router/routerConfig/SettingsRoutes.tsx'; +import RouterPaths from '@app/router/routerPathsConfig'; +import PrivateRouteWrapper from '@app/router/PrivateRouterWrapper.tsx'; +import Spinner from '@shared/ui/spinner'; + +const Login = lazy(() => import('@pages/login')); +const Page404 = lazy(() => import('@pages/404')); + +const Router: FC = () => { + const routes = createRoutesFromElements( + }> + + + }> + } /> + }> + }> + {ClustersRoutes()} + {OperationsRoutes()} + {SettingsRoutes()} + + + } /> + } /> + {/* anything that starts with "/" i.e. "/any-page" */} + , + ); + + const browserRouter = createBrowserRouter(routes); + + return ; +}; + +export default Router; diff --git a/console/ui/src/app/router/routerConfig/ClustersRoutes.tsx b/console/ui/src/app/router/routerConfig/ClustersRoutes.tsx new file mode 100644 index 000000000..29e0f62c4 --- /dev/null +++ b/console/ui/src/app/router/routerConfig/ClustersRoutes.tsx @@ -0,0 +1,37 @@ +import { lazy } from 'react'; +import { Navigate, Route } from 'react-router-dom'; +import RouterPaths from '@app/router/routerPathsConfig'; + +const Clusters = lazy(() => import('@pages/clusters')); +const AddCluster = lazy(() => import('@pages/add-cluster')); +const OverviewCluster = lazy(() => import('@pages/overview-cluster')); + +const ClustersRoutes = () => ( + + {/*redirects to "clusters" when opening homepage*/} + } /> + + } /> + } + /> + } + /> + + +); + +export default ClustersRoutes; diff --git a/console/ui/src/app/router/routerConfig/OperationsRoutes.tsx b/console/ui/src/app/router/routerConfig/OperationsRoutes.tsx new file mode 100644 index 000000000..fda368640 --- /dev/null +++ b/console/ui/src/app/router/routerConfig/OperationsRoutes.tsx @@ -0,0 +1,29 @@ +import { lazy } from 'react'; +import { Route } from 'react-router-dom'; +import RouterPaths from '@app/router/routerPathsConfig'; +import OperationLog from '@pages/operation-log'; + +const Operations = lazy(() => import('@pages/operations')); + +const OperationsRoutes = () => ( + + + } /> + `${data.operationId}`, + }, + }} + element={} + /> + + +); + +export default OperationsRoutes; diff --git a/console/ui/src/app/router/routerConfig/SettingsRoutes.tsx b/console/ui/src/app/router/routerConfig/SettingsRoutes.tsx new file mode 100644 index 000000000..3b013d146 --- /dev/null +++ b/console/ui/src/app/router/routerConfig/SettingsRoutes.tsx @@ -0,0 +1,28 @@ +import { lazy } from 'react'; +import { Navigate, Route } from 'react-router-dom'; +import RouterPaths from '@app/router/routerPathsConfig'; + +const Settings = lazy(() => import('@pages/settings')); +const SettingsForm = lazy(() => import('@widgets/settings-form')); +const SecretsTable = lazy(() => import('@widgets/secrets-table/ui')); +const ProjectsTable = lazy(() => import('@widgets/projects-table')); +const EnvironmentsTable = lazy(() => import('@widgets/environments-table')); + +const SettingsRoutes = () => ( + + }> + }> + } /> + } /> + } /> + } /> + + +); + +export default SettingsRoutes; diff --git a/console/ui/src/app/router/routerPathsConfig/index.ts b/console/ui/src/app/router/routerPathsConfig/index.ts new file mode 100644 index 000000000..3563dc35a --- /dev/null +++ b/console/ui/src/app/router/routerPathsConfig/index.ts @@ -0,0 +1,20 @@ +import routerClustersPathsConfig from '@app/router/routerPathsConfig/routerClustersPathsConfig.ts'; +import routerOperationsPathsConfig from '@app/router/routerPathsConfig/routerOperationsPathsConfig.ts'; +import routerSettingsPathsConfig from '@app/router/routerPathsConfig/routerSettingsPathsConfig.ts'; + +/* + Combines route paths into one config + */ +const RouterPaths = { + login: { + absolutePath: 'login', + }, + notFound: { + absolutePath: 'notFound', + }, + clusters: routerClustersPathsConfig, + operations: routerOperationsPathsConfig, + settings: routerSettingsPathsConfig, +} as const; + +export default RouterPaths; diff --git a/console/ui/src/app/router/routerPathsConfig/routerClustersPathsConfig.ts b/console/ui/src/app/router/routerPathsConfig/routerClustersPathsConfig.ts new file mode 100644 index 000000000..a75dc7858 --- /dev/null +++ b/console/ui/src/app/router/routerPathsConfig/routerClustersPathsConfig.ts @@ -0,0 +1,13 @@ +const routerClustersPathsConfig = { + absolutePath: '/clusters', + add: { + absolutePath: '/clusters/add', + relativePath: 'add', + }, + overview: { + absolutePath: '/clusters/:clusterId/overview', + relativePath: ':clusterId/overview', + }, +}; + +export default routerClustersPathsConfig; diff --git a/console/ui/src/app/router/routerPathsConfig/routerOperationsPathsConfig.ts b/console/ui/src/app/router/routerPathsConfig/routerOperationsPathsConfig.ts new file mode 100644 index 000000000..f8cdb3398 --- /dev/null +++ b/console/ui/src/app/router/routerPathsConfig/routerOperationsPathsConfig.ts @@ -0,0 +1,9 @@ +const routerOperationsPathsConfig = { + absolutePath: '/operations', + log: { + absolutePath: '/operations/:operationId/log', + relativePath: ':operationId/log', + }, +}; + +export default routerOperationsPathsConfig; diff --git a/console/ui/src/app/router/routerPathsConfig/routerSettingsPathsConfig.ts b/console/ui/src/app/router/routerPathsConfig/routerSettingsPathsConfig.ts new file mode 100644 index 000000000..7df9c59a0 --- /dev/null +++ b/console/ui/src/app/router/routerPathsConfig/routerSettingsPathsConfig.ts @@ -0,0 +1,21 @@ +const routerSettingsPathsConfig = { + absolutePath: '/settings', + general: { + absolutePath: '/settings/general', + relativePath: 'general', + }, + secrets: { + absolutePath: '/settings/secrets', + relativePath: 'secrets', + }, + projects: { + absolutePath: '/settings/projects', + relativePath: 'projects', + }, + environments: { + absolutePath: '/settings/environments', + relativePath: 'environments', + }, +}; + +export default routerSettingsPathsConfig; diff --git a/console/ui/src/app/vite-env.d.ts b/console/ui/src/app/vite-env.d.ts new file mode 100644 index 000000000..b1f45c786 --- /dev/null +++ b/console/ui/src/app/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/console/ui/src/entities/authentification-method-form-block/index.ts b/console/ui/src/entities/authentification-method-form-block/index.ts new file mode 100644 index 000000000..7aa00aa2c --- /dev/null +++ b/console/ui/src/entities/authentification-method-form-block/index.ts @@ -0,0 +1,3 @@ +import AuthenticationMethodFormBlock from '@entities/authentification-method-form-block/ui'; + +export default AuthenticationMethodFormBlock; diff --git a/console/ui/src/entities/authentification-method-form-block/model/constants.ts b/console/ui/src/entities/authentification-method-form-block/model/constants.ts new file mode 100644 index 000000000..1a05b07e0 --- /dev/null +++ b/console/ui/src/entities/authentification-method-form-block/model/constants.ts @@ -0,0 +1,16 @@ +import { TFunction } from 'i18next'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; + +export const authenticationMethods = (t: TFunction) => + Object.freeze([ + { + id: AUTHENTICATION_METHODS.SSH, + name: t('sshKey', { ns: 'clusters' }), + description: t('sshKeyAuthDescription', { ns: 'clusters' }), + }, + { + id: AUTHENTICATION_METHODS.PASSWORD, + name: t('password', { ns: 'shared' }), + description: t('passwordAuthDescription', { ns: 'clusters' }), + }, + ]); diff --git a/console/ui/src/entities/authentification-method-form-block/ui/AuthenticationFormPart.tsx b/console/ui/src/entities/authentification-method-form-block/ui/AuthenticationFormPart.tsx new file mode 100644 index 000000000..2fb7f2c21 --- /dev/null +++ b/console/ui/src/entities/authentification-method-form-block/ui/AuthenticationFormPart.tsx @@ -0,0 +1,45 @@ +import React, { FC } from 'react'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; +import SshMethodFormPart from '@entities/authentification-method-form-block/ui/SshMethodFormPart.tsx'; +import PasswordMethodFormPart from '@entities/authentification-method-form-block/ui/PasswordMethodFormPart.tsx'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; +import { TextField } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +const AuthenticationFormPart: FC = () => { + const { t } = useTranslation('shared'); + const { + control, + watch, + formState: { errors }, + } = useFormContext(); + + const watchAuthenticationMethod = watch(CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD); + const watchIsSaveToConsole = watch(CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_IS_SAVE_TO_CONSOLE); + + return ( + <> + ( + + )} + /> + {watchAuthenticationMethod === AUTHENTICATION_METHODS.SSH ? : } + + ); +}; + +export default AuthenticationFormPart; diff --git a/console/ui/src/entities/authentification-method-form-block/ui/PasswordMethodFormPart.tsx b/console/ui/src/entities/authentification-method-form-block/ui/PasswordMethodFormPart.tsx new file mode 100644 index 000000000..fb3a3746c --- /dev/null +++ b/console/ui/src/entities/authentification-method-form-block/ui/PasswordMethodFormPart.tsx @@ -0,0 +1,37 @@ +import React, { FC } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { TextField } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const PasswordMethodFormPart: FC = () => { + const { t } = useTranslation('shared'); + + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + <> + ( + + )} + /> + + ); +}; + +export default PasswordMethodFormPart; diff --git a/console/ui/src/entities/authentification-method-form-block/ui/SshMethodFormPart.tsx b/console/ui/src/entities/authentification-method-form-block/ui/SshMethodFormPart.tsx new file mode 100644 index 000000000..57aaaae53 --- /dev/null +++ b/console/ui/src/entities/authentification-method-form-block/ui/SshMethodFormPart.tsx @@ -0,0 +1,40 @@ +import React, { FC } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { TextField } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const SshMethodFormPart: FC = () => { + const { t } = useTranslation(['clusters', 'shared', 'settings']); + + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + <> + ( + + )} + /> + + ); +}; + +export default SshMethodFormPart; diff --git a/console/ui/src/entities/authentification-method-form-block/ui/index.tsx b/console/ui/src/entities/authentification-method-form-block/ui/index.tsx new file mode 100644 index 000000000..6c0a27fde --- /dev/null +++ b/console/ui/src/entities/authentification-method-form-block/ui/index.tsx @@ -0,0 +1,187 @@ +import React, { useEffect } from 'react'; +import { Box, Checkbox, FormControlLabel, MenuItem, Radio, Stack, TextField, Typography } from '@mui/material'; +import { authenticationMethods } from '@entities/authentification-method-form-block/model/constants.ts'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { useGetSecretsQuery } from '@shared/api/api/secrets.ts'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import AuthenticationFormPart from '@entities/authentification-method-form-block/ui/AuthenticationFormPart.tsx'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; + +const AuthenticationMethodFormBlock: React.FC = () => { + const { t } = useTranslation(['clusters', 'shared', 'settings']); + + const { + control, + watch, + resetField, + setValue, + formState: { errors }, + } = useFormContext(); + + const currentProject = useAppSelector(selectCurrentProject); + + const watchAuthenticationMethod = watch(CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD); + const watchIsSaveToConsole = watch(CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_IS_SAVE_TO_CONSOLE); + const watchIsUseDefinedSecret = watch(CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET); + + const secrets = useGetSecretsQuery({ type: watchAuthenticationMethod, projectId: currentProject }); + + useEffect(() => { + resetField(CLUSTER_FORM_FIELD_NAMES.SECRET_ID); + }, [watchIsUseDefinedSecret, watchAuthenticationMethod]); + + useEffect(() => { + setValue(CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET, !!secrets.data?.data?.length); + }, [secrets.data?.data?.length]); + + return ( + + + {t('authenticationMethod', { ns: 'clusters' })} + + + + ( + <> + {authenticationMethods(t).map((method) => ( + onChange(method.id)}> + + + {method.name} + {method.description} + + + ))} + + )} + /> + + {secrets.data?.data?.length ? ( + <> + ( + + {[t('yes', { ns: 'shared' }), t('no', { ns: 'shared' })].map((option) => ( + + {option} + + ))} + + )} + /> + {watchIsUseDefinedSecret ? ( + <> + {watchAuthenticationMethod === AUTHENTICATION_METHODS.SSH ? ( + ( + + )} + /> + ) : null} + ( + + {secrets.data.data.map((secret) => ( + + {secret?.name} + + ))} + + )} + /> + + ) : ( + + )} + + ) : ( + + )} + {(secrets.data?.data?.length && !watchIsUseDefinedSecret) || !secrets.data?.data?.length ? ( + <> + {watchIsSaveToConsole ? ( + ( + + )} + /> + ) : null} + ( + } + checked={value as boolean} + onChange={onChange} + label={t('saveToConsole')} + /> + )} + /> + + ) : null} + + + ); +}; + +export default AuthenticationMethodFormBlock; diff --git a/console/ui/src/entities/breadcumb-item/index.ts b/console/ui/src/entities/breadcumb-item/index.ts new file mode 100644 index 000000000..5e908adaa --- /dev/null +++ b/console/ui/src/entities/breadcumb-item/index.ts @@ -0,0 +1,3 @@ +import BreadcrumbsItem from '@entities/breadcumb-item/ui'; + +export default BreadcrumbsItem; diff --git a/console/ui/src/entities/breadcumb-item/model/types.ts b/console/ui/src/entities/breadcumb-item/model/types.ts new file mode 100644 index 000000000..1d1de41e9 --- /dev/null +++ b/console/ui/src/entities/breadcumb-item/model/types.ts @@ -0,0 +1,4 @@ +export interface BreadcrumbsItemProps { + label: string; + path: string; +} diff --git a/console/ui/src/entities/breadcumb-item/ui/index.tsx b/console/ui/src/entities/breadcumb-item/ui/index.tsx new file mode 100644 index 000000000..f66077840 --- /dev/null +++ b/console/ui/src/entities/breadcumb-item/ui/index.tsx @@ -0,0 +1,11 @@ +import { FC } from 'react'; +import { Link } from '@mui/material'; +import { BreadcrumbsItemProps } from '@entities/breadcumb-item/model/types.ts'; + +const BreadcrumbsItem: FC = ({ label, path }) => ( + + {label} + +); + +export default BreadcrumbsItem; diff --git a/console/ui/src/entities/cluster-cloud-provider-block/index.ts b/console/ui/src/entities/cluster-cloud-provider-block/index.ts new file mode 100644 index 000000000..cd7e326e9 --- /dev/null +++ b/console/ui/src/entities/cluster-cloud-provider-block/index.ts @@ -0,0 +1,3 @@ +import ClusterFormCloudProviderBox from '@entities/cluster-cloud-provider-block/ui'; + +export default ClusterFormCloudProviderBox; diff --git a/console/ui/src/entities/cluster-cloud-provider-block/model/types.ts b/console/ui/src/entities/cluster-cloud-provider-block/model/types.ts new file mode 100644 index 000000000..e4466d11e --- /dev/null +++ b/console/ui/src/entities/cluster-cloud-provider-block/model/types.ts @@ -0,0 +1,6 @@ +import { ReactElement } from 'react'; + +export interface ClusterFormCloudProviderBoxProps { + children?: ReactElement; + isActive?: boolean; +} diff --git a/console/ui/src/entities/cluster-cloud-provider-block/ui/index.tsx b/console/ui/src/entities/cluster-cloud-provider-block/ui/index.tsx new file mode 100644 index 000000000..6c5797e65 --- /dev/null +++ b/console/ui/src/entities/cluster-cloud-provider-block/ui/index.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; +import { ClusterFormCloudProviderBoxProps } from '@entities/cluster-cloud-provider-block/model/types.ts'; +import SelectableBox from '@shared/ui/selectable-box'; + +const ClusterFormCloudProviderBox: FC = ({ children, isActive, ...props }) => { + return ( + + {children} + + ); +}; + +export default ClusterFormCloudProviderBox; diff --git a/console/ui/src/entities/cluster-description-block/index.ts b/console/ui/src/entities/cluster-description-block/index.ts new file mode 100644 index 000000000..8670af328 --- /dev/null +++ b/console/ui/src/entities/cluster-description-block/index.ts @@ -0,0 +1,3 @@ +import ClusterDescriptionBlock from '@entities/cluster-description-block/ui'; + +export default ClusterDescriptionBlock; diff --git a/console/ui/src/entities/cluster-description-block/ui/index.tsx b/console/ui/src/entities/cluster-description-block/ui/index.tsx new file mode 100644 index 000000000..54fb36a6c --- /dev/null +++ b/console/ui/src/entities/cluster-description-block/ui/index.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Box, TextField, Typography } from '@mui/material'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; + +const ClusterDescriptionBlock: React.FC = () => { + const { t } = useTranslation('clusters'); + const { control } = useFormContext(); + + return ( + + + {t('description')} + + ( + + )} + /> + + ); +}; + +export default ClusterDescriptionBlock; diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/assets/aws.svg b/console/ui/src/entities/cluster-form-cloud-region-block/assets/aws.svg new file mode 100644 index 000000000..b3049e1b1 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/assets/aws.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/assets/azure.svg b/console/ui/src/entities/cluster-form-cloud-region-block/assets/azure.svg new file mode 100644 index 000000000..3948dff06 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/assets/azure.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/assets/digitalocean.svg b/console/ui/src/entities/cluster-form-cloud-region-block/assets/digitalocean.svg new file mode 100644 index 000000000..b27205b5b --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/assets/digitalocean.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/assets/gcp.svg b/console/ui/src/entities/cluster-form-cloud-region-block/assets/gcp.svg new file mode 100644 index 000000000..973d10529 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/assets/gcp.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/assets/hetzner.svg b/console/ui/src/entities/cluster-form-cloud-region-block/assets/hetzner.svg new file mode 100644 index 000000000..328106191 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/assets/hetzner.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/index.ts b/console/ui/src/entities/cluster-form-cloud-region-block/index.ts new file mode 100644 index 000000000..eab9a9bd2 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/index.ts @@ -0,0 +1,3 @@ +import CloudFormRegionBlock from '@entities/cluster-form-cloud-region-block/ui'; + +export default CloudFormRegionBlock; diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/lib/hooks.tsx b/console/ui/src/entities/cluster-form-cloud-region-block/lib/hooks.tsx new file mode 100644 index 000000000..f179eb74d --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/lib/hooks.tsx @@ -0,0 +1,15 @@ +import AWSIcon from '../assets/aws.svg'; +import GCPIcon from '../assets/gcp.svg'; +import AzureIcon from '../assets/azure.svg'; +import DigitalOceanIcon from '../assets/digitalocean.svg'; +import HetznerIcon from '../assets/hetzner.svg'; +import { PROVIDERS } from '@shared/config/constants.ts'; + +export const useNameIconProvidersMap = () => ({ + // TODO: refactor into moving from hooks to constant + [PROVIDERS.AWS]: AWSIcon, + [PROVIDERS.GCP]: GCPIcon, + [PROVIDERS.AZURE]: AzureIcon, + [PROVIDERS.DIGITAL_OCEAN]: DigitalOceanIcon, + [PROVIDERS.HETZNER]: HetznerIcon, +}); diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/model/types.ts b/console/ui/src/entities/cluster-form-cloud-region-block/model/types.ts new file mode 100644 index 000000000..502161210 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/model/types.ts @@ -0,0 +1,5 @@ +import { DeploymentInfoCloudRegion } from '@shared/api/api/other.ts'; + +export interface CloudFormRegionBlockProps { + regions: DeploymentInfoCloudRegion[]; +} diff --git a/console/ui/src/entities/cluster-form-cloud-region-block/ui/index.tsx b/console/ui/src/entities/cluster-form-cloud-region-block/ui/index.tsx new file mode 100644 index 000000000..709eaf2f2 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cloud-region-block/ui/index.tsx @@ -0,0 +1,81 @@ +import { FC, SyntheticEvent } from 'react'; +import { TabContext, TabList, TabPanel } from '@mui/lab'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { Box, Divider, Stack, Tab, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import ClusterFormRegionConfigBox from '@widgets/cluster-form/ui/ClusterFormRegionConfigBox.tsx'; + +const CloudFormRegionBlock: FC = () => { + const { t } = useTranslation('clusters'); + const { control, watch, setValue } = useFormContext(); + + const watchProvider = watch(CLUSTER_FORM_FIELD_NAMES.PROVIDER); + const regionWatch = watch(CLUSTER_FORM_FIELD_NAMES.REGION); + + const regions = watchProvider?.cloud_regions ?? []; + + const handleRegionChange = + (onChange: (...event: never[]) => void) => (e: SyntheticEvent, value: string) => { + onChange(value); + setValue( + CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG, + regions?.find((region) => region.code === value)?.datacenters?.[0], + ); + }; + + const handleRegionConfigChange = (onChange: (...event: never[]) => void, value: string) => () => { + onChange(value); + }; + + return ( + + + {t('selectCloudRegion')} + + + { + return ( + + {regions.map((region) => ( + + ))} + + ); + }} + /> + + { + return ( + <> + {regions.map((region) => ( + + + {region.datacenters.map((config) => ( + + ))} + + + ))} + + ); + }} + /> + + + ); +}; + +export default CloudFormRegionBlock; diff --git a/console/ui/src/entities/cluster-form-cluster-name-block/index.ts b/console/ui/src/entities/cluster-form-cluster-name-block/index.ts new file mode 100644 index 000000000..79a75cb09 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cluster-name-block/index.ts @@ -0,0 +1,3 @@ +import ClusterFormClusterNameBlock from '@entities/cluster-form-cluster-name-block/ui'; + +export default ClusterFormClusterNameBlock; diff --git a/console/ui/src/entities/cluster-form-cluster-name-block/ui/index.tsx b/console/ui/src/entities/cluster-form-cluster-name-block/ui/index.tsx new file mode 100644 index 000000000..2f7519643 --- /dev/null +++ b/console/ui/src/entities/cluster-form-cluster-name-block/ui/index.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Box, TextField, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; + +const ClusterFormClusterNameBlock: React.FC = () => { + const { t } = useTranslation('clusters'); + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + + {t('clusterName')}* + + ( + + )} + /> + + ); +}; + +export default ClusterFormClusterNameBlock; diff --git a/console/ui/src/entities/cluster-form-environment-block/index.ts b/console/ui/src/entities/cluster-form-environment-block/index.ts new file mode 100644 index 000000000..051571223 --- /dev/null +++ b/console/ui/src/entities/cluster-form-environment-block/index.ts @@ -0,0 +1,3 @@ +import ClusterFormEnvironmentBlock from '@entities/cluster-form-environment-block/ui'; + +export default ClusterFormEnvironmentBlock; diff --git a/console/ui/src/entities/cluster-form-environment-block/model/types.ts b/console/ui/src/entities/cluster-form-environment-block/model/types.ts new file mode 100644 index 000000000..3cf7df829 --- /dev/null +++ b/console/ui/src/entities/cluster-form-environment-block/model/types.ts @@ -0,0 +1,5 @@ +import { ResponseEnvironment } from '@shared/api/api/environments.ts'; + +export interface EnvironmentBlockProps { + environments: ResponseEnvironment[]; +} diff --git a/console/ui/src/entities/cluster-form-environment-block/ui/index.tsx b/console/ui/src/entities/cluster-form-environment-block/ui/index.tsx new file mode 100644 index 000000000..6b4286fb2 --- /dev/null +++ b/console/ui/src/entities/cluster-form-environment-block/ui/index.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { Box, MenuItem, Select, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { EnvironmentBlockProps } from '@entities/cluster-form-environment-block/model/types.ts'; + +const ClusterFormEnvironmentBlock: FC = ({ environments }) => { + const { t } = useTranslation('shared'); + const { control } = useFormContext(); + + return ( + + + {t('environment')} + + ( + + )} + /> + + ); +}; + +export default ClusterFormEnvironmentBlock; diff --git a/console/ui/src/entities/cluster-form-instances-amount-block/assets/instancesIcon.svg b/console/ui/src/entities/cluster-form-instances-amount-block/assets/instancesIcon.svg new file mode 100644 index 000000000..be31ac321 --- /dev/null +++ b/console/ui/src/entities/cluster-form-instances-amount-block/assets/instancesIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/console/ui/src/entities/cluster-form-instances-amount-block/index.ts b/console/ui/src/entities/cluster-form-instances-amount-block/index.ts new file mode 100644 index 000000000..6bc1e3a93 --- /dev/null +++ b/console/ui/src/entities/cluster-form-instances-amount-block/index.ts @@ -0,0 +1,3 @@ +import InstancesAmountBlock from '@entities/cluster-form-instances-amount-block/ui'; + +export default InstancesAmountBlock; diff --git a/console/ui/src/entities/cluster-form-instances-amount-block/ui/index.tsx b/console/ui/src/entities/cluster-form-instances-amount-block/ui/index.tsx new file mode 100644 index 000000000..e8c64732e --- /dev/null +++ b/console/ui/src/entities/cluster-form-instances-amount-block/ui/index.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Box, Typography } from '@mui/material'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import ClusterSliderBox from '@shared/ui/slider-box'; +import ServersIcon from '@shared/assets/serversIcon.svg?react'; + +const InstancesAmountBlock: FC = () => { + const { t } = useTranslation('clusters'); + + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + + {t('numberOfInstances')} + + ( + } + error={errors[CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT]} + /> + )} + /> + + ); +}; + +export default InstancesAmountBlock; diff --git a/console/ui/src/entities/cluster-form-instances-block/index.ts b/console/ui/src/entities/cluster-form-instances-block/index.ts new file mode 100644 index 000000000..6fbdf71ba --- /dev/null +++ b/console/ui/src/entities/cluster-form-instances-block/index.ts @@ -0,0 +1,3 @@ +import CloudFormInstancesBlock from '@entities/cluster-form-instances-block/ui'; + +export default CloudFormInstancesBlock; diff --git a/console/ui/src/entities/cluster-form-instances-block/model/types.ts b/console/ui/src/entities/cluster-form-instances-block/model/types.ts new file mode 100644 index 000000000..b560ec09a --- /dev/null +++ b/console/ui/src/entities/cluster-form-instances-block/model/types.ts @@ -0,0 +1,9 @@ +import { DeploymentInstanceType } from '@shared/api/api/other.ts'; + +export interface CloudFormInstancesBlockProps { + instances: { + small?: DeploymentInstanceType[]; + medium?: DeploymentInstanceType[]; + large?: DeploymentInstanceType[]; + }; +} diff --git a/console/ui/src/entities/cluster-form-instances-block/ui/index.tsx b/console/ui/src/entities/cluster-form-instances-block/ui/index.tsx new file mode 100644 index 000000000..3183228a5 --- /dev/null +++ b/console/ui/src/entities/cluster-form-instances-block/ui/index.tsx @@ -0,0 +1,79 @@ +import { FC } from 'react'; +import { TabContext, TabList, TabPanel } from '@mui/lab'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { Box, Divider, Stack, Tab, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import ClusterFromInstanceConfigBox from '@entities/cluster-instance-config-box'; + +const CloudFormInstancesBlock: FC = () => { + const { t } = useTranslation('clusters'); + const { control, watch, setValue } = useFormContext(); + + const watchInstanceType = watch(CLUSTER_FORM_FIELD_NAMES.INSTANCE_TYPE); + + const watchProvider = watch(CLUSTER_FORM_FIELD_NAMES.PROVIDER); + + const instances = watchProvider?.instance_types ?? []; + + const handleInstanceTypeChange = (onChange: (...event: any[]) => void) => (_: any, value: string) => { + onChange(value); + setValue(CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG, instances?.[value]?.[0]); + }; + + const handleInstanceConfigChange = (onChange: (...event: any[]) => void, value: string) => () => { + onChange(value); + }; + + return ( + + + {t('selectInstanceType')} + + + { + return ( + + {Object.entries(instances)?.map(([key, value]) => + value ? : null, + )} + + ); + }} + /> + + { + return ( + <> + {Object.entries(instances).map(([key, configs]) => ( + + + {configs?.map((config) => ( + + ))} + + + ))} + + ); + }} + /> + + + ); +}; + +export default CloudFormInstancesBlock; diff --git a/console/ui/src/entities/cluster-info/index.ts b/console/ui/src/entities/cluster-info/index.ts new file mode 100644 index 000000000..bbce068bb --- /dev/null +++ b/console/ui/src/entities/cluster-info/index.ts @@ -0,0 +1,3 @@ +import ClusterInfo from '@entities/cluster-info/ui'; + +export default ClusterInfo; diff --git a/console/ui/src/entities/cluster-info/lib/hooks.tsx b/console/ui/src/entities/cluster-info/lib/hooks.tsx new file mode 100644 index 000000000..599e76772 --- /dev/null +++ b/console/ui/src/entities/cluster-info/lib/hooks.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from 'react-i18next'; +import { Typography } from '@mui/material'; +import { ClusterInfoProps } from '@entities/cluster-info/model/types.ts'; + +export const useGetClusterInfoConfig = ({ + postgresVersion, + clusterName, + description, + environment, + location, +}: ClusterInfoProps) => { + const { t } = useTranslation(['clusters', 'shared']); + + return [ + { + title: t('postgresVersion', { ns: 'clusters' }), + children: {postgresVersion}, + }, + { + title: t('clusterName', { ns: 'clusters' }), + children: {clusterName}, + }, + { + title: t('description', { ns: 'shared' }), + children: {description ?? '---'}, + }, + { + title: t('environment', { ns: 'shared' }), + children: {environment}, + }, + ...(location + ? [ + { + title: t('location', { ns: 'clusters' }), + children: {location}, + }, + ] + : []), + ]; +}; diff --git a/console/ui/src/entities/cluster-info/model/types.ts b/console/ui/src/entities/cluster-info/model/types.ts new file mode 100644 index 000000000..ca8b3e36c --- /dev/null +++ b/console/ui/src/entities/cluster-info/model/types.ts @@ -0,0 +1,7 @@ +export interface ClusterInfoProps { + postgresVersion?: number; + clusterName?: string; + description?: string; + environment?: string; + location?: string; +} diff --git a/console/ui/src/entities/cluster-info/ui/index.tsx b/console/ui/src/entities/cluster-info/ui/index.tsx new file mode 100644 index 000000000..ca9b24e10 --- /dev/null +++ b/console/ui/src/entities/cluster-info/ui/index.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { ClusterInfoProps } from '@entities/cluster-info/model/types.ts'; +import EditNoteOutlinedIcon from '@mui/icons-material/EditNoteOutlined'; +import { useGetClusterInfoConfig } from '@entities/cluster-info/lib/hooks.tsx'; +import InfoCardBody from '@shared/ui/info-card-body'; + +const ClusterInfo: FC = ({ postgresVersion, clusterName, description, environment, location }) => { + const { t } = useTranslation(['clusters', 'shared']); + + const config = useGetClusterInfoConfig({ + postgresVersion, + clusterName, + description, + environment, + location, + }); + + return ( + + }> + + {t('clusterInfo')} + + + + + + ); +}; + +export default ClusterInfo; diff --git a/console/ui/src/entities/cluster-instance-config-box/index.ts b/console/ui/src/entities/cluster-instance-config-box/index.ts new file mode 100644 index 000000000..9c916eca5 --- /dev/null +++ b/console/ui/src/entities/cluster-instance-config-box/index.ts @@ -0,0 +1,3 @@ +import ClusterFromInstanceConfigBox from '@entities/cluster-instance-config-box/ui'; + +export default ClusterFromInstanceConfigBox; diff --git a/console/ui/src/entities/cluster-instance-config-box/model/types.ts b/console/ui/src/entities/cluster-instance-config-box/model/types.ts new file mode 100644 index 000000000..b02047b62 --- /dev/null +++ b/console/ui/src/entities/cluster-instance-config-box/model/types.ts @@ -0,0 +1,7 @@ +import { ClusterFormSelectableBoxProps } from '@shared/ui/selectable-box/model/types.ts'; + +export interface ClusterFromInstanceConfigBoxProps extends ClusterFormSelectableBoxProps { + name: string; + cpu: string; + ram: string; +} diff --git a/console/ui/src/entities/cluster-instance-config-box/ui/index.tsx b/console/ui/src/entities/cluster-instance-config-box/ui/index.tsx new file mode 100644 index 000000000..75fdad778 --- /dev/null +++ b/console/ui/src/entities/cluster-instance-config-box/ui/index.tsx @@ -0,0 +1,32 @@ +import { FC } from 'react'; +import { ClusterFromInstanceConfigBoxProps } from '@entities/cluster-instance-config-box/model/types.ts'; +import SelectableBox from '@shared/ui/selectable-box'; +import { Box, Stack, Typography } from '@mui/material'; +import RamIcon from '@shared/assets/ramIcon.svg?react'; +import CpuIcon from '@shared/assets/cpuIcon.svg?react'; + +const ClusterFromInstanceConfigBox: FC = ({ + name, + cpu, + ram, + isActive, + ...props +}) => ( + + + {name} + + + + + {cpu} CPU + + + + {ram} GB RAM + + + +); + +export default ClusterFromInstanceConfigBox; diff --git a/console/ui/src/entities/cluster-name-description-block/index.ts b/console/ui/src/entities/cluster-name-description-block/index.ts new file mode 100644 index 000000000..d150a3dc7 --- /dev/null +++ b/console/ui/src/entities/cluster-name-description-block/index.ts @@ -0,0 +1,3 @@ +import ClusterNameDescriptionBlock from '@entities/cluster-name-description-block/ui'; + +export default ClusterNameDescriptionBlock; diff --git a/console/ui/src/entities/cluster-name-description-block/ui/index.tsx b/console/ui/src/entities/cluster-name-description-block/ui/index.tsx new file mode 100644 index 000000000..efef382dc --- /dev/null +++ b/console/ui/src/entities/cluster-name-description-block/ui/index.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Box, TextField, Typography } from '@mui/material'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; + +const ClusterNameDescriptionBlock: React.FC = () => { + const { t } = useTranslation('clusters'); + const { control } = useFormContext(); + + return ( + + + {t('description')} + + ( + + )} + /> + + ); +}; + +export default ClusterNameDescriptionBlock; diff --git a/console/ui/src/entities/connection-info/assets/eyeIcon.svg b/console/ui/src/entities/connection-info/assets/eyeIcon.svg new file mode 100644 index 000000000..08e322d38 --- /dev/null +++ b/console/ui/src/entities/connection-info/assets/eyeIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/console/ui/src/entities/connection-info/index.ts b/console/ui/src/entities/connection-info/index.ts new file mode 100644 index 000000000..880b22c06 --- /dev/null +++ b/console/ui/src/entities/connection-info/index.ts @@ -0,0 +1,3 @@ +import ConnectionInfo from '@entities/connection-info/ui'; + +export default ConnectionInfo; diff --git a/console/ui/src/entities/connection-info/lib/hooks.tsx b/console/ui/src/entities/connection-info/lib/hooks.tsx new file mode 100644 index 000000000..3ab9bdd48 --- /dev/null +++ b/console/ui/src/entities/connection-info/lib/hooks.tsx @@ -0,0 +1,65 @@ +import { useTranslation } from 'react-i18next'; +import { Stack, Typography } from '@mui/material'; +import { useState } from 'react'; +import CopyIcon from '@shared/ui/copy-icon'; +import EyeIcon from '../assets/eyeIcon.svg?react'; +import ConnectionInfoRowContainer from '@entities/connection-info/ui/ConnectionInfoRowConteiner.tsx'; +import { ConnectionInfoProps } from '@entities/connection-info/model/types.ts'; + +export const useGetConnectionInfoConfig = ({ connectionInfo }: { connectionInfo: ConnectionInfoProps }) => { + const { t } = useTranslation(['clusters', 'shared']); + const [isPasswordHidden, setIsPasswordHidden] = useState(true); + + const togglePasswordVisibility = () => setIsPasswordHidden((prev) => !prev); + + const renderCollection = (collection: string | object, defaultLabel: string) => { + return ['string', 'number'].includes(typeof collection) + ? [ + { + title: defaultLabel, + children: ( + + {collection} + + ), + }, + ] + : typeof collection === 'object' + ? Object.entries(collection)?.map(([key, value]) => ({ + title: `${defaultLabel} ${key}`, + children: ( + + {value} + + ), + })) ?? [] + : []; + }; + + return [ + ...(connectionInfo?.address ? renderCollection(connectionInfo.address, t('address', { ns: 'shared' })) : []), + ...(connectionInfo?.port ? renderCollection(connectionInfo.port, t('port', { ns: 'clusters' })) : []), + { + title: t('user', { ns: 'shared' }), + children: ( + + {connectionInfo?.superuser} + + ), + }, + { + title: t('password', { ns: 'shared' }), + children: ( + + + {isPasswordHidden ? connectionInfo?.password?.replace(/./g, '*') : connectionInfo?.password} + + + + + + + ), + }, + ]; +}; diff --git a/console/ui/src/entities/connection-info/model/types.ts b/console/ui/src/entities/connection-info/model/types.ts new file mode 100644 index 000000000..13a0db1a0 --- /dev/null +++ b/console/ui/src/entities/connection-info/model/types.ts @@ -0,0 +1,14 @@ +import { ReactNode } from 'react'; + +export interface ConnectionInfoProps { + connectionInfo?: { + address?: string | Record; + port?: string | Record; + superuser?: string; + password?: string; + }; +} + +export interface ConnectionInfoRowContainerProps { + children: ReactNode; +} diff --git a/console/ui/src/entities/connection-info/ui/ConnectionInfoRowConteiner.tsx b/console/ui/src/entities/connection-info/ui/ConnectionInfoRowConteiner.tsx new file mode 100644 index 000000000..2b2214e2b --- /dev/null +++ b/console/ui/src/entities/connection-info/ui/ConnectionInfoRowConteiner.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { Stack } from '@mui/material'; +import { ConnectionInfoRowContainerProps } from '@entities/connection-info/model/types.ts'; + +const ConnectionInfoRowContainer: FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export default ConnectionInfoRowContainer; diff --git a/console/ui/src/entities/connection-info/ui/index.tsx b/console/ui/src/entities/connection-info/ui/index.tsx new file mode 100644 index 000000000..e15e1797d --- /dev/null +++ b/console/ui/src/entities/connection-info/ui/index.tsx @@ -0,0 +1,28 @@ +import { FC } from 'react'; +import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { ConnectionInfoProps } from '@entities/connection-info/model/types.ts'; +import PowerOutlinedIcon from '@mui/icons-material/PowerOutlined'; +import { useGetConnectionInfoConfig } from '@entities/connection-info/lib/hooks.tsx'; +import InfoCardBody from '@shared/ui/info-card-body'; + +const ConnectionInfo: FC = ({ connectionInfo }) => { + const { t } = useTranslation(['clusters', 'shared']); + + const config = useGetConnectionInfoConfig({ connectionInfo }); + + return ( + + }> + + {t('connectionInfo')} + + + + + + ); +}; + +export default ConnectionInfo; diff --git a/console/ui/src/entities/database-servers-block/index.ts b/console/ui/src/entities/database-servers-block/index.ts new file mode 100644 index 000000000..b9a1caa2e --- /dev/null +++ b/console/ui/src/entities/database-servers-block/index.ts @@ -0,0 +1,3 @@ +import DatabaseServersBlock from '@entities/database-servers-block/ui'; + +export default DatabaseServersBlock; diff --git a/console/ui/src/entities/database-servers-block/model/types.ts b/console/ui/src/entities/database-servers-block/model/types.ts new file mode 100644 index 000000000..08dd769ca --- /dev/null +++ b/console/ui/src/entities/database-servers-block/model/types.ts @@ -0,0 +1,6 @@ +import { UseFieldArrayRemove } from 'react-hook-form'; + +export interface DatabaseServerBlockProps { + index: number; + remove?: UseFieldArrayRemove; +} diff --git a/console/ui/src/entities/database-servers-block/ui/DatabaseServerBox.tsx b/console/ui/src/entities/database-servers-block/ui/DatabaseServerBox.tsx new file mode 100644 index 000000000..074907f70 --- /dev/null +++ b/console/ui/src/entities/database-servers-block/ui/DatabaseServerBox.tsx @@ -0,0 +1,81 @@ +import { FC } from 'react'; +import { DatabaseServerBlockProps } from '@entities/database-servers-block/model/types.ts'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { Card, IconButton, Stack, TextField, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import CloseIcon from '@mui/icons-material/Close'; + +const DatabaseServerBox: FC = ({ index, remove }) => { + const { t } = useTranslation(['clusters', 'shared']); + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + {remove ? ( + + + + ) : null} + + {`${t('server', { ns: 'clusters' })} ${index + 1}`} + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + + ); +}; + +export default DatabaseServerBox; diff --git a/console/ui/src/entities/database-servers-block/ui/index.tsx b/console/ui/src/entities/database-servers-block/ui/index.tsx new file mode 100644 index 000000000..570d9709f --- /dev/null +++ b/console/ui/src/entities/database-servers-block/ui/index.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react'; +import { useFieldArray } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import DatabaseServerBox from '@entities/database-servers-block/ui/DatabaseServerBox.tsx'; +import { Box, Button, Stack, Typography } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import { useTranslation } from 'react-i18next'; + +const DatabaseServersBlock: FC = () => { + const { t } = useTranslation('clusters'); + const { fields, append, remove } = useFieldArray({ + name: CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS, + }); + + const removeServer = (index: number) => () => remove(index); + + return ( + + + {t('databaseServers')} + + + + {fields.map((field, index) => ( + + ))} + + + + + ); +}; + +export default DatabaseServersBlock; diff --git a/console/ui/src/entities/load-balancers-block/index.ts b/console/ui/src/entities/load-balancers-block/index.ts new file mode 100644 index 000000000..3006f3b89 --- /dev/null +++ b/console/ui/src/entities/load-balancers-block/index.ts @@ -0,0 +1,3 @@ +import LoadBalancersBlock from '@entities/load-balancers-block/ui'; + +export default LoadBalancersBlock; diff --git a/console/ui/src/entities/load-balancers-block/ui/index.tsx b/console/ui/src/entities/load-balancers-block/ui/index.tsx new file mode 100644 index 000000000..5295688e0 --- /dev/null +++ b/console/ui/src/entities/load-balancers-block/ui/index.tsx @@ -0,0 +1,32 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Box, Checkbox, Stack, Tooltip, Typography } from '@mui/material'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; + +const LoadBalancersBlock: FC = () => { + const { t } = useTranslation('clusters'); + const { control } = useFormContext(); + + return ( + + + {t('loadBalancers')} + + + {t('haproxyLoadBalancer')} + + + + } + /> + + + ); +}; + +export default LoadBalancersBlock; diff --git a/console/ui/src/entities/postgres-version-block/index.ts b/console/ui/src/entities/postgres-version-block/index.ts new file mode 100644 index 000000000..d11dc47b0 --- /dev/null +++ b/console/ui/src/entities/postgres-version-block/index.ts @@ -0,0 +1,3 @@ +import PostgresVersionBox from '@entities/postgres-version-block/ui'; + +export default PostgresVersionBox; diff --git a/console/ui/src/entities/postgres-version-block/model/types.ts b/console/ui/src/entities/postgres-version-block/model/types.ts new file mode 100644 index 000000000..845ecc4c8 --- /dev/null +++ b/console/ui/src/entities/postgres-version-block/model/types.ts @@ -0,0 +1,5 @@ +import { ResponsePostgresVersion } from '@shared/api/api/other.ts'; + +export interface PostgresVersionBlockProps { + postgresVersions: ResponsePostgresVersion[]; +} diff --git a/console/ui/src/entities/postgres-version-block/ui/index.tsx b/console/ui/src/entities/postgres-version-block/ui/index.tsx new file mode 100644 index 000000000..23bc1a952 --- /dev/null +++ b/console/ui/src/entities/postgres-version-block/ui/index.tsx @@ -0,0 +1,43 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Box, MenuItem, Select, Typography } from '@mui/material'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { PostgresVersionBlockProps } from '@entities/postgres-version-block/model/types.ts'; + +const PostgresVersionBox: FC = ({ postgresVersions }) => { + const { t } = useTranslation('clusters'); + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + + {t('postgresVersion')} + + ( + + )} + /> + + ); +}; + +export default PostgresVersionBox; diff --git a/console/ui/src/entities/providers-block/index.ts b/console/ui/src/entities/providers-block/index.ts new file mode 100644 index 000000000..7c5ed31e0 --- /dev/null +++ b/console/ui/src/entities/providers-block/index.ts @@ -0,0 +1,3 @@ +import ClusterFormProvidersBlock from '@entities/providers-block/ui'; + +export default ClusterFormProvidersBlock; diff --git a/console/ui/src/entities/providers-block/model/types.ts b/console/ui/src/entities/providers-block/model/types.ts new file mode 100644 index 000000000..cf51b516b --- /dev/null +++ b/console/ui/src/entities/providers-block/model/types.ts @@ -0,0 +1,10 @@ +import { ReactElement } from 'react'; + +export interface ProvidersBlockProps { + providers: { code?: string; description?: string }[]; +} + +export interface ClusterFormCloudProviderBoxProps { + children?: ReactElement; + isActive?: boolean; +} diff --git a/console/ui/src/entities/providers-block/ui/ClusterFormCloudProviderBox.tsx b/console/ui/src/entities/providers-block/ui/ClusterFormCloudProviderBox.tsx new file mode 100644 index 000000000..eb86ba3bb --- /dev/null +++ b/console/ui/src/entities/providers-block/ui/ClusterFormCloudProviderBox.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; +import SelectableBox from '@shared/ui/selectable-box'; +import { ClusterFormCloudProviderBoxProps } from '@entities/providers-block/model/types.ts'; + +const ClusterFormCloudProviderBox: FC = ({ children, isActive, ...props }) => { + return ( + + {children} + + ); +}; + +export default ClusterFormCloudProviderBox; diff --git a/console/ui/src/entities/providers-block/ui/index.tsx b/console/ui/src/entities/providers-block/ui/index.tsx new file mode 100644 index 000000000..bbecb6df2 --- /dev/null +++ b/console/ui/src/entities/providers-block/ui/index.tsx @@ -0,0 +1,81 @@ +import { FC } from 'react'; +import { Box, Stack, Tooltip, Typography } from '@mui/material'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { useTranslation } from 'react-i18next'; +import { useNameIconProvidersMap } from '@entities/cluster-form-cloud-region-block/lib/hooks.tsx'; +import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; +import ServersIcon from '@shared/assets/serversIcon.svg?react'; +import theme from '@shared/theme/theme.ts'; +import { ProvidersBlockProps } from '@entities/providers-block/model/types.ts'; +import { PROVIDERS } from '@shared/config/constants.ts'; +import ClusterFormCloudProviderBox from '@entities/providers-block/ui/ClusterFormCloudProviderBox.tsx'; + +const ClusterFormProvidersBlock: FC = ({ providers }) => { + const { t } = useTranslation('clusters'); + const { control, reset } = useFormContext(); + + const nameIconProvidersMap = useNameIconProvidersMap(); + + const handleProviderChange = (value) => () => { + reset((values) => ({ + ...values, + [CLUSTER_FORM_FIELD_NAMES.PROVIDER]: value, + [CLUSTER_FORM_FIELD_NAMES.REGION]: value?.cloud_regions?.[0]?.code, + [CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG]: value?.cloud_regions?.[0]?.datacenters?.[0], + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_TYPE]: 'small', + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]: value?.instance_types?.small?.[0], + [CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT]: + value?.volumes?.find((volume) => volume?.is_default)?.min_size < 100 + ? 100 + : value?.volumes?.find((volume) => volume?.is_default)?.min_size, + })); + }; + + return ( + + + {t('selectDeploymentDestination')} + + ( + + {providers.map((provider) => ( + + {provider.description} + + ))} + + + + + + + + + + {t('yourOwn')} + + + {t('machines')} + + + + + + + )} + /> + + ); +}; + +export default ClusterFormProvidersBlock; diff --git a/console/ui/src/entities/secret-form-block/index.ts b/console/ui/src/entities/secret-form-block/index.ts new file mode 100644 index 000000000..f2fd1ffa4 --- /dev/null +++ b/console/ui/src/entities/secret-form-block/index.ts @@ -0,0 +1,3 @@ +import SecretFormBlock from '@entities/secret-form-block/ui'; + +export default SecretFormBlock; diff --git a/console/ui/src/entities/secret-form-block/lib/functions.ts b/console/ui/src/entities/secret-form-block/lib/functions.ts new file mode 100644 index 000000000..b00615d25 --- /dev/null +++ b/console/ui/src/entities/secret-form-block/lib/functions.ts @@ -0,0 +1,118 @@ +import { PROVIDERS } from '@shared/config/constants.ts'; +import AwsSecretBlock from '@entities/secret-form-block/ui/AwsSecret.tsx'; +import GcpSecretBlock from '@entities/secret-form-block/ui/GcpSecret.tsx'; +import AzureSecretBlock from '@entities/secret-form-block/ui/AzureSecret.tsx'; +import DoSecretBlock from '@entities/secret-form-block/ui/DigitalOceanSecret.tsx'; +import HetznerSecretBlock from '@entities/secret-form-block/ui/HetznerSecret.tsx'; +import SshKeySecretBlock from '@entities/secret-form-block/ui/SshKeySecret.tsx'; +import PasswordSecretBlock from '@entities/secret-form-block/ui/PasswordSecret.tsx'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; +import { SecretFormValues } from '@entities/secret-form-block/model/types.ts'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +export const getAddSecretFormContentByType = (type: string) => { + switch (type) { + case PROVIDERS.AWS: + return { + translationKey: 'settingsAwsSecretInfo', + link: 'https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html', + formComponent: AwsSecretBlock, + }; + case PROVIDERS.GCP: + return { + translationKey: 'settingsGcpSecretInfo', + link: 'https://cloud.google.com/iam/docs/keys-create-delete', + formComponent: GcpSecretBlock, + }; + case PROVIDERS.AZURE: + return { + translationKey: 'settingsAzureSecretInfo', + link: 'https://learn.microsoft.com/en-us/azure/developer/ansible/create-ansible-service-principal?tabs=azure-cli', + formComponent: AzureSecretBlock, + }; + case PROVIDERS.DIGITAL_OCEAN: + return { + translationKey: 'settingsDoSecretInfo', + link: 'https://docs.digitalocean.com/reference/api/create-personal-access-token/', + formComponent: DoSecretBlock, + }; + case PROVIDERS.HETZNER: + return { + translationKey: 'settingsHetznerSecretInfo', + link: 'https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/', + formComponent: HetznerSecretBlock, + }; + case AUTHENTICATION_METHODS.SSH: + return { + translationKey: 'settingsSshKeySecretInfo', + formComponent: SshKeySecretBlock, + }; + default: + return { + translationKey: 'settingsPasswordSecretInfo', + formComponent: PasswordSecretBlock, + }; + } +}; + +export const getSecretBodyFromValues = (values: SecretFormValues) => { + switch (values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE]) { + case PROVIDERS.AWS: + return { + [PROVIDERS.AWS]: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_ACCESS_KEY_ID]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_ACCESS_KEY_ID], + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_SECRET_ACCESS_KEY]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_SECRET_ACCESS_KEY], + }, + }; + case PROVIDERS.GCP: + return { + [PROVIDERS.GCP]: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.GCP_SERVICE_ACCOUNT_CONTENTS]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.GCP_SERVICE_ACCOUNT_CONTENTS], + }, + }; + case PROVIDERS.DIGITAL_OCEAN: + return { + [PROVIDERS.DIGITAL_OCEAN]: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.DO_API_TOKEN]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.DO_API_TOKEN], + }, + }; + case PROVIDERS.AZURE: + return { + [PROVIDERS.AZURE]: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SUBSCRIPTION_ID]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SUBSCRIPTION_ID], + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_CLIENT_ID]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_CLIENT_ID], + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SECRET]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SECRET], + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_TENANT]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_TENANT], + }, + }; + case PROVIDERS.HETZNER: + return { + [PROVIDERS.HETZNER]: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.HCLOUD_API_TOKEN]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.HCLOUD_API_TOKEN], + }, + }; + case AUTHENTICATION_METHODS.SSH: + return { + [AUTHENTICATION_METHODS.SSH]: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SSH_PRIVATE_KEY]: + values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SSH_PRIVATE_KEY], + }, + }; + case AUTHENTICATION_METHODS.PASSWORD: + return { + [AUTHENTICATION_METHODS.PASSWORD]: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME]: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME], + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD]: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD], + }, + }; + } +}; diff --git a/console/ui/src/entities/secret-form-block/model/constants.ts b/console/ui/src/entities/secret-form-block/model/constants.ts new file mode 100644 index 000000000..dcb23babe --- /dev/null +++ b/console/ui/src/entities/secret-form-block/model/constants.ts @@ -0,0 +1,30 @@ +export const SECRET_MODAL_CONTENT_CLOUD_PROVIDERS_FORM_FIELD_NAMES = Object.freeze({ + // changing names or keys might break 'secrets' POST request + AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID', + AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY', + GCP_SERVICE_ACCOUNT_CONTENTS: 'GCP_SERVICE_ACCOUNT_CONTENTS', + DO_API_TOKEN: 'DO_API_TOKEN', + AZURE_SUBSCRIPTION_ID: 'AZURE_SUBSCRIPTION_ID', + AZURE_CLIENT_ID: 'AZURE_CLIENT_ID', + AZURE_SECRET: 'AZURE_SECRET', + AZURE_TENANT: 'AZURE_TENANT', + HCLOUD_API_TOKEN: 'HCLOUD_API_TOKEN', +}); + +export const SECRET_MODAL_CONTENT_LOCAL_FORM_FIELDS = Object.freeze({ + SSH_PRIVATE_KEY: 'SSH_PRIVATE_KEY', + USERNAME: 'USERNAME', + PASSWORD: 'PASSWORD', +}); + +export const SECRET_MODAL_CONTENT_BODY_FORM_FIELDS = Object.freeze({ + ...SECRET_MODAL_CONTENT_CLOUD_PROVIDERS_FORM_FIELD_NAMES, + ...SECRET_MODAL_CONTENT_LOCAL_FORM_FIELDS, +}); + +export const SECRET_MODAL_CONTENT_FORM_FIELD_NAMES = Object.freeze({ + // changing names might break 'secrets' POST request + SECRET_TYPE: 'type', + SECRET_NAME: 'name', + ...SECRET_MODAL_CONTENT_BODY_FORM_FIELDS, +}); diff --git a/console/ui/src/entities/secret-form-block/model/types.ts b/console/ui/src/entities/secret-form-block/model/types.ts new file mode 100644 index 000000000..5657eee4c --- /dev/null +++ b/console/ui/src/entities/secret-form-block/model/types.ts @@ -0,0 +1,28 @@ +import { PROVIDERS } from '@shared/config/constants.ts'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +export interface SecretFormBlockProps { + secretType: (typeof PROVIDERS)[keyof typeof PROVIDERS]; + isAdditionalInfoDisplayed?: boolean; +} + +export interface SecretFormValues { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE]: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_NAME]: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_ACCESS_KEY_ID]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_SECRET_ACCESS_KEY]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.GCP_SERVICE_ACCOUNT_CONTENTS]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.DO_API_TOKEN]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SUBSCRIPTION_ID]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_CLIENT_ID]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SECRET]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_TENANT]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.HCLOUD_API_TOKEN]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SSH_PRIVATE_KEY]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME]?: string; + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD]?: string; +} + +export interface SecretModalContentProps { + secretType: string; +} diff --git a/console/ui/src/entities/secret-form-block/ui/AwsSecret.tsx b/console/ui/src/entities/secret-form-block/ui/AwsSecret.tsx new file mode 100644 index 000000000..a6f4c1653 --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/AwsSecret.tsx @@ -0,0 +1,49 @@ +import { FC } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Stack, TextField } from '@mui/material'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const AwsSecretBlock: FC = () => { + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + ( + + )} + /> + ( + + )} + /> + + ); +}; + +export default AwsSecretBlock; diff --git a/console/ui/src/entities/secret-form-block/ui/AzureSecret.tsx b/console/ui/src/entities/secret-form-block/ui/AzureSecret.tsx new file mode 100644 index 000000000..e0cc1ec2d --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/AzureSecret.tsx @@ -0,0 +1,79 @@ +import { FC } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Stack, TextField } from '@mui/material'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const AzureSecretBlock: FC = () => { + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + ); +}; + +export default AzureSecretBlock; diff --git a/console/ui/src/entities/secret-form-block/ui/DigitalOceanSecret.tsx b/console/ui/src/entities/secret-form-block/ui/DigitalOceanSecret.tsx new file mode 100644 index 000000000..92406140d --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/DigitalOceanSecret.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Stack, TextField } from '@mui/material'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const DoSecretBlock: FC = () => { + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + ( + + )} + /> + + ); +}; + +export default DoSecretBlock; diff --git a/console/ui/src/entities/secret-form-block/ui/GcpSecret.tsx b/console/ui/src/entities/secret-form-block/ui/GcpSecret.tsx new file mode 100644 index 000000000..b3bdc4824 --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/GcpSecret.tsx @@ -0,0 +1,36 @@ +import { FC } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Stack, TextField } from '@mui/material'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const GcpSecretBlock: FC = () => { + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + ( + + )} + /> + + ); +}; + +export default GcpSecretBlock; diff --git a/console/ui/src/entities/secret-form-block/ui/HetznerSecret.tsx b/console/ui/src/entities/secret-form-block/ui/HetznerSecret.tsx new file mode 100644 index 000000000..1eae92059 --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/HetznerSecret.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Stack, TextField } from '@mui/material'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const HetznerSecretBlock: React.FC = () => { + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + ( + + )} + /> + + ); +}; + +export default HetznerSecretBlock; diff --git a/console/ui/src/entities/secret-form-block/ui/PasswordSecret.tsx b/console/ui/src/entities/secret-form-block/ui/PasswordSecret.tsx new file mode 100644 index 000000000..2127a1cdc --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/PasswordSecret.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Stack, TextField } from '@mui/material'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const PasswordSecretBlock: React.FC = () => { + const { t } = useTranslation('shared'); + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + ( + + )} + /> + ( + + )} + /> + + ); +}; + +export default PasswordSecretBlock; diff --git a/console/ui/src/entities/secret-form-block/ui/SshKeySecret.tsx b/console/ui/src/entities/secret-form-block/ui/SshKeySecret.tsx new file mode 100644 index 000000000..88dc63ca2 --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/SshKeySecret.tsx @@ -0,0 +1,38 @@ +import { FC } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { Stack, TextField } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const SshKeySecretBlock: FC = () => { + const { t } = useTranslation('settings'); + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + ( + + )} + /> + + ); +}; + +export default SshKeySecretBlock; diff --git a/console/ui/src/entities/secret-form-block/ui/index.tsx b/console/ui/src/entities/secret-form-block/ui/index.tsx new file mode 100644 index 000000000..8bdf97780 --- /dev/null +++ b/console/ui/src/entities/secret-form-block/ui/index.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { getAddSecretFormContentByType } from '@entities/secret-form-block/lib/functions.ts'; +import { Link, Stack, Typography } from '@mui/material'; +import { SecretFormBlockProps } from '@entities/secret-form-block/model/types.ts'; + +const SecretFormBlock: React.FC = ({ secretType, isAdditionalInfoDisplayed = false }) => { + const { t } = useTranslation('settings'); + + const content = getAddSecretFormContentByType(secretType); + + return ( + + + {content.link ? ( + + + + ) : ( + t(content.translationKey) + )} + + + {isAdditionalInfoDisplayed ? ( + {t('settingsConfidentialDataStore')} + ) : null} + + ); +}; +export default SecretFormBlock; diff --git a/console/ui/src/entities/settings-proxy-block/index.ts b/console/ui/src/entities/settings-proxy-block/index.ts new file mode 100644 index 000000000..f1e0d2d02 --- /dev/null +++ b/console/ui/src/entities/settings-proxy-block/index.ts @@ -0,0 +1,3 @@ +import SettingsProxyBlock from '@entities/settings-proxy-block/ui'; + +export default SettingsProxyBlock; diff --git a/console/ui/src/entities/settings-proxy-block/model/constants.ts b/console/ui/src/entities/settings-proxy-block/model/constants.ts new file mode 100644 index 000000000..c87aad173 --- /dev/null +++ b/console/ui/src/entities/settings-proxy-block/model/constants.ts @@ -0,0 +1,4 @@ +export const SETTINGS_FORM_FIELDS_NAMES = Object.freeze({ + HTTP_PROXY: 'http_proxy', + HTTPS_PROXY: 'https_proxy', +}); diff --git a/console/ui/src/entities/settings-proxy-block/model/types.ts b/console/ui/src/entities/settings-proxy-block/model/types.ts new file mode 100644 index 000000000..9217a87b8 --- /dev/null +++ b/console/ui/src/entities/settings-proxy-block/model/types.ts @@ -0,0 +1,6 @@ +import { SETTINGS_FORM_FIELDS_NAMES } from '@entities/settings-proxy-block/model/constants.ts'; + +export interface SettingsFormValues { + [SETTINGS_FORM_FIELDS_NAMES.HTTP_PROXY]: string; + [SETTINGS_FORM_FIELDS_NAMES.HTTPS_PROXY]: string; +} diff --git a/console/ui/src/entities/settings-proxy-block/ui/index.tsx b/console/ui/src/entities/settings-proxy-block/ui/index.tsx new file mode 100644 index 000000000..c074d50cb --- /dev/null +++ b/console/ui/src/entities/settings-proxy-block/ui/index.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Stack, TextField, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { SETTINGS_FORM_FIELDS_NAMES } from '@entities/settings-proxy-block/model/constants.ts'; + +const SettingsProxyBlock: React.FC = () => { + const { t } = useTranslation('settings'); + + const { control } = useFormContext(); + + return ( + + + {t('proxyServer')} + + {t('proxyServerInfo')} + + ( + + http_proxy + + + )} + /> + ( + + https_proxy + + + )} + /> + + + ); +}; + +export default SettingsProxyBlock; diff --git a/console/ui/src/entities/sidebar-item/index.ts b/console/ui/src/entities/sidebar-item/index.ts new file mode 100644 index 000000000..cdfcd55a4 --- /dev/null +++ b/console/ui/src/entities/sidebar-item/index.ts @@ -0,0 +1,3 @@ +import SidebarItem from './ui'; + +export default SidebarItem; diff --git a/console/ui/src/entities/sidebar-item/model/types.ts b/console/ui/src/entities/sidebar-item/model/types.ts new file mode 100644 index 000000000..91a81cae3 --- /dev/null +++ b/console/ui/src/entities/sidebar-item/model/types.ts @@ -0,0 +1,8 @@ +export interface SidebarItemProps { + path: string; + label: string; + icon?: Element; + isActive?: string; + isCollapsed?: boolean; + target?: string; +} diff --git a/console/ui/src/entities/sidebar-item/ui/SidebarItemContent.tsx b/console/ui/src/entities/sidebar-item/ui/SidebarItemContent.tsx new file mode 100644 index 000000000..1645c0890 --- /dev/null +++ b/console/ui/src/entities/sidebar-item/ui/SidebarItemContent.tsx @@ -0,0 +1,36 @@ +import { FC } from 'react'; +import { Link } from 'react-router-dom'; +import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import theme from '@shared/theme/theme.ts'; +import { SidebarItemProps } from '@entities/sidebar-item/model/types.ts'; + +const SidebarItemContent: FC = ({ + path, + label, + icon: SidebarIcon, + isActive, + target, + isCollapsed, +}) => { + return ( + + + {SidebarIcon ? : null} + + {!isCollapsed ? : null} + + ); +}; + +export default SidebarItemContent; diff --git a/console/ui/src/entities/sidebar-item/ui/index.tsx b/console/ui/src/entities/sidebar-item/ui/index.tsx new file mode 100644 index 000000000..0231b2ec1 --- /dev/null +++ b/console/ui/src/entities/sidebar-item/ui/index.tsx @@ -0,0 +1,36 @@ +import { FC } from 'react'; +import { SidebarItemProps } from '../model/types.ts'; +import { Box, ListItem, Tooltip, useTheme } from '@mui/material'; +import SidebarItemContent from '@entities/sidebar-item/ui/SidebarItemContent.tsx'; + +const SidebarItem: FC = ({ path, label, icon, isActive, isCollapsed = false, target, ...props }) => { + const theme = useTheme(); + + return ( + + {isCollapsed ? ( + + + + + + ) : ( + + )} + + ); +}; + +export default SidebarItem; diff --git a/console/ui/src/entities/ssh-key-block/index.ts b/console/ui/src/entities/ssh-key-block/index.ts new file mode 100644 index 000000000..f9d2e7755 --- /dev/null +++ b/console/ui/src/entities/ssh-key-block/index.ts @@ -0,0 +1,3 @@ +import ClusterFormSshKeyBlock from '@entities/ssh-key-block/ui'; + +export default ClusterFormSshKeyBlock; diff --git a/console/ui/src/entities/ssh-key-block/ui/index.tsx b/console/ui/src/entities/ssh-key-block/ui/index.tsx new file mode 100644 index 000000000..0944603cc --- /dev/null +++ b/console/ui/src/entities/ssh-key-block/ui/index.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react'; +import { Box, TextField, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; + +const ClusterFormSshKeyBlock: FC = () => { + const { t } = useTranslation('clusters'); + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + + {t('sshPublicKey')}* + + ( + + )} + /> + + ); +}; + +export default ClusterFormSshKeyBlock; diff --git a/console/ui/src/entities/storage-block/index.ts b/console/ui/src/entities/storage-block/index.ts new file mode 100644 index 000000000..ca4917b81 --- /dev/null +++ b/console/ui/src/entities/storage-block/index.ts @@ -0,0 +1,3 @@ +import StorageBlock from '@entities/storage-block/ui'; + +export default StorageBlock; diff --git a/console/ui/src/entities/storage-block/lib/functions.ts b/console/ui/src/entities/storage-block/lib/functions.ts new file mode 100644 index 000000000..e69de29bb diff --git a/console/ui/src/entities/storage-block/ui/index.tsx b/console/ui/src/entities/storage-block/ui/index.tsx new file mode 100644 index 000000000..47b93cbf9 --- /dev/null +++ b/console/ui/src/entities/storage-block/ui/index.tsx @@ -0,0 +1,50 @@ +import { FC } from 'react'; +import { Box, Typography } from '@mui/material'; +import ClusterSliderBox from '@shared/ui/slider-box'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import StorageIcon from '@shared/assets/storageIcon.svg?react'; + +const StorageBlock: FC = () => { + const { t } = useTranslation('clusters'); + + const { + control, + watch, + formState: { errors }, + } = useFormContext(); + + const watchProvider = watch(CLUSTER_FORM_FIELD_NAMES.PROVIDER); + + const storage = watchProvider?.volumes?.find((volume) => volume?.is_default) ?? {}; + + return ( + + + {t('dataDiskStorage')} + + ( + } + error={errors[CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT]} + /> + )} + /> + + ); +}; + +export default StorageBlock; diff --git a/console/ui/src/entities/vip-address-block/index.ts b/console/ui/src/entities/vip-address-block/index.ts new file mode 100644 index 000000000..ac25bc850 --- /dev/null +++ b/console/ui/src/entities/vip-address-block/index.ts @@ -0,0 +1,3 @@ +import VipAddressBlock from '@entities/vip-address-block/ui'; + +export default VipAddressBlock; diff --git a/console/ui/src/entities/vip-address-block/ui/index.tsx b/console/ui/src/entities/vip-address-block/ui/index.tsx new file mode 100644 index 000000000..2bf7aebf7 --- /dev/null +++ b/console/ui/src/entities/vip-address-block/ui/index.tsx @@ -0,0 +1,38 @@ +import React, { FC } from 'react'; +import { Box, TextField, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Controller, useFormContext } from 'react-hook-form'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; + +const VipAddressBlock: FC = () => { + const { t } = useTranslation('clusters'); + const { + control, + formState: { errors }, + } = useFormContext(); + + return ( + + + {t('clusterVipAddress')} + + ( + + )} + /> + + ); +}; + +export default VipAddressBlock; diff --git a/console/ui/src/features/add-environment/index.ts b/console/ui/src/features/add-environment/index.ts new file mode 100644 index 000000000..4cd74ca65 --- /dev/null +++ b/console/ui/src/features/add-environment/index.ts @@ -0,0 +1,3 @@ +import AddEnvironment from '@features/add-environment/ui'; + +export default AddEnvironment; diff --git a/console/ui/src/features/add-environment/ui/index.tsx b/console/ui/src/features/add-environment/ui/index.tsx new file mode 100644 index 000000000..e4d746bc6 --- /dev/null +++ b/console/ui/src/features/add-environment/ui/index.tsx @@ -0,0 +1,41 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import SettingsAddEntity from '@shared/ui/settings-add-entity/ui'; +import { usePostEnvironmentsMutation } from '@shared/api/api/environments.ts'; +import { AddEntityFormValues } from '@shared/ui/settings-add-entity/model/types.ts'; +import { ADD_ENTITY_FORM_NAMES } from '@shared/ui/settings-add-entity/model/constants.ts'; + +const AddEnvironment: FC = () => { + const { t } = useTranslation('settings'); + + const [postEnvironmentTrigger, postEnvironmentTriggerState] = usePostEnvironmentsMutation(); + + const onSubmit = async (values: AddEntityFormValues) => { + await postEnvironmentTrigger({ + requestEnvironment: { + name: values[ADD_ENTITY_FORM_NAMES.NAME], + description: values[ADD_ENTITY_FORM_NAMES.DESCRIPTION], + }, + }).unwrap(); + toast.success( + t('environmentSuccessfullyCreated', { + ns: 'toasts', + environmentName: values[ADD_ENTITY_FORM_NAMES.NAME], + }), + ); + }; + + return ( + + ); +}; + +export default AddEnvironment; diff --git a/console/ui/src/features/add-project/index.ts b/console/ui/src/features/add-project/index.ts new file mode 100644 index 000000000..5b56920db --- /dev/null +++ b/console/ui/src/features/add-project/index.ts @@ -0,0 +1,3 @@ +import AddProject from '@features/add-project/ui'; + +export default AddProject; diff --git a/console/ui/src/features/add-project/model/constants.ts b/console/ui/src/features/add-project/model/constants.ts new file mode 100644 index 000000000..e7ca72c01 --- /dev/null +++ b/console/ui/src/features/add-project/model/constants.ts @@ -0,0 +1,4 @@ +export const PROJECT_FORM_NAMES = Object.freeze({ + NAME: 'name', + DESCRIPTION: 'description', +}); diff --git a/console/ui/src/features/add-project/model/types.ts b/console/ui/src/features/add-project/model/types.ts new file mode 100644 index 000000000..427c0c177 --- /dev/null +++ b/console/ui/src/features/add-project/model/types.ts @@ -0,0 +1,6 @@ +import { PROJECT_FORM_NAMES } from '@features/add-project/model/constants.ts'; + +export interface ProjectFormValues { + [PROJECT_FORM_NAMES.NAME]: string; + [PROJECT_FORM_NAMES.NAME]: string; +} diff --git a/console/ui/src/features/add-project/model/validation.ts b/console/ui/src/features/add-project/model/validation.ts new file mode 100644 index 000000000..0ecdb5d9f --- /dev/null +++ b/console/ui/src/features/add-project/model/validation.ts @@ -0,0 +1,9 @@ +import * as yup from 'yup'; +import { TFunction } from 'i18next'; +import { PROJECT_FORM_NAMES } from '@features/add-project/model/constants.ts'; + +export const AddProjectFormSchema = (t: TFunction) => + yup.object({ + [PROJECT_FORM_NAMES.NAME]: yup.string().required(t('requiredField', { ns: 'validation' })), + [PROJECT_FORM_NAMES.DESCRIPTION]: yup.string(), + }); diff --git a/console/ui/src/features/add-project/ui/index.tsx b/console/ui/src/features/add-project/ui/index.tsx new file mode 100644 index 000000000..18ae968d8 --- /dev/null +++ b/console/ui/src/features/add-project/ui/index.tsx @@ -0,0 +1,54 @@ +import React, { FC } from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { usePostProjectsMutation } from '@shared/api/api/projects.ts'; +import { yupResolver } from '@hookform/resolvers/yup'; + +import { ProjectFormValues } from '@features/add-project/model/types.ts'; +import { toast } from 'react-toastify'; +import { AddProjectFormSchema } from '@features/add-project/model/validation.ts'; +import SettingsAddEntity from '@shared/ui/settings-add-entity/ui'; +import { ADD_ENTITY_FORM_NAMES } from '@shared/ui/settings-add-entity/model/constants.ts'; + +const AddProject: FC = () => { + const { t } = useTranslation(['settings', 'toasts']); + + const [postProjectTrigger, postProjectTriggerState] = usePostProjectsMutation(); + + const { + control, + handleSubmit, + formState: { isValid, isSubmitting }, + } = useForm({ + mode: 'all', + resolver: yupResolver(AddProjectFormSchema(t)), + }); + + const onSubmit = async (values: ProjectFormValues) => { + await postProjectTrigger({ + requestProjectCreate: { + name: values[ADD_ENTITY_FORM_NAMES.NAME], + description: values[ADD_ENTITY_FORM_NAMES.DESCRIPTION], + }, + }).unwrap(); + toast.success( + t('projectSuccessfullyCreated', { + ns: 'toasts', + projectName: values[ADD_ENTITY_FORM_NAMES.NAME], + }), + ); + }; + + return ( + + ); +}; + +export default AddProject; diff --git a/console/ui/src/features/add-secret/index.ts b/console/ui/src/features/add-secret/index.ts new file mode 100644 index 000000000..bb2061a68 --- /dev/null +++ b/console/ui/src/features/add-secret/index.ts @@ -0,0 +1,3 @@ +import SettingsAddSecret from '@features/add-secret/ui'; + +export default SettingsAddSecret; diff --git a/console/ui/src/features/add-secret/model/constants.ts b/console/ui/src/features/add-secret/model/constants.ts new file mode 100644 index 000000000..3001d4304 --- /dev/null +++ b/console/ui/src/features/add-secret/model/constants.ts @@ -0,0 +1,6 @@ +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +export const ADD_SECRET_FORM_FIELD_NAMES = Object.freeze({ + SECRET_NAME: 'secretName', + ...SECRET_MODAL_CONTENT_FORM_FIELD_NAMES, +}); diff --git a/console/ui/src/features/add-secret/model/types.ts b/console/ui/src/features/add-secret/model/types.ts new file mode 100644 index 000000000..750b1b2e0 --- /dev/null +++ b/console/ui/src/features/add-secret/model/types.ts @@ -0,0 +1,7 @@ +import { ADD_SECRET_FORM_FIELD_NAMES } from '@features/add-secret/model/constants.ts'; + +import { SecretFormValues } from '@entities/secret-form-block/model/types.ts'; + +export interface AddSecretFormValues extends SecretFormValues { + [ADD_SECRET_FORM_FIELD_NAMES.SECRET_NAME]: string; +} diff --git a/console/ui/src/features/add-secret/model/validation.ts b/console/ui/src/features/add-secret/model/validation.ts new file mode 100644 index 000000000..a03daa589 --- /dev/null +++ b/console/ui/src/features/add-secret/model/validation.ts @@ -0,0 +1,49 @@ +import * as yup from 'yup'; +import { TFunction } from 'i18next'; +import { PROVIDERS } from '@shared/config/constants.ts'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const requiredField = ({ valueToBeRequired, t }: { valueToBeRequired: string; t: TFunction }) => + yup + .mixed() + .when(SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE, ([secretType]) => + secretType === valueToBeRequired + ? yup.string().required(t('requiredField', { ns: 'validation' })) + : yup.mixed().optional(), + ); + +export const AddSecretFormSchema = (t: TFunction) => + yup.object({ + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE]: yup.string().required(), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_NAME]: yup + .string() + .required(t('requiredField', { ns: 'validation' })), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_ACCESS_KEY_ID]: requiredField({ valueToBeRequired: PROVIDERS.AWS, t }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AWS_SECRET_ACCESS_KEY]: requiredField({ + valueToBeRequired: PROVIDERS.AWS, + t, + }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.GCP_SERVICE_ACCOUNT_CONTENTS]: requiredField({ + valueToBeRequired: PROVIDERS.GCP, + t, + }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SUBSCRIPTION_ID]: requiredField({ + valueToBeRequired: PROVIDERS.AZURE, + t, + }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_CLIENT_ID]: requiredField({ valueToBeRequired: PROVIDERS.AZURE, t }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_SECRET]: requiredField({ valueToBeRequired: PROVIDERS.AZURE, t }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.AZURE_TENANT]: requiredField({ valueToBeRequired: PROVIDERS.AZURE, t }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.DO_API_TOKEN]: requiredField({ + valueToBeRequired: PROVIDERS.DIGITAL_OCEAN, + t, + }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.HCLOUD_API_TOKEN]: requiredField({ + valueToBeRequired: PROVIDERS.HETZNER, + t, + }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SSH_PRIVATE_KEY]: requiredField({ valueToBeRequired: 'ssh_key', t }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME]: requiredField({ valueToBeRequired: 'password', t }), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD]: requiredField({ valueToBeRequired: 'password', t }), + }); diff --git a/console/ui/src/features/add-secret/ui/index.tsx b/console/ui/src/features/add-secret/ui/index.tsx new file mode 100644 index 000000000..328c93d2e --- /dev/null +++ b/console/ui/src/features/add-secret/ui/index.tsx @@ -0,0 +1,150 @@ +import React, { useState } from 'react'; +import { Button, Card, CircularProgress, MenuItem, Modal, Select, Stack, TextField, Typography } from '@mui/material'; +import AddBoxOutlinedIcon from '@mui/icons-material/AddBoxOutlined'; +import { useTranslation } from 'react-i18next'; +import { Controller, FormProvider, useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { AddSecretFormSchema } from '@features/add-secret/model/validation.ts'; +import { PROVIDERS } from '@shared/config/constants.ts'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import { usePostSecretsMutation } from '@shared/api/api/secrets.ts'; +import { LoadingButton } from '@mui/lab'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; +import { toast } from 'react-toastify'; +import SecretFormBlock from '@entities/secret-form-block/ui'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; +import { SecretFormValues } from '@entities/secret-form-block/model/types.ts'; +import { getSecretBodyFromValues } from '@entities/secret-form-block/lib/functions.ts'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; + +const SettingsAddSecret: React.FC = () => { + const { t } = useTranslation(['settings', 'validation', 'toasts']); + const currentProject = useAppSelector(selectCurrentProject); + + const [isModalOpen, setIsModalOpen] = useState(false); + + const handleModalOpenState = (isOpen: boolean) => () => setIsModalOpen(isOpen); + + const [postSecretTrigger, postSecretTriggerState] = usePostSecretsMutation(); + + const methods = useForm({ + mode: 'all', + resolver: yupResolver(AddSecretFormSchema(t)), + defaultValues: { + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE]: '', + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_NAME]: '', + }, + }); + + const watchType = methods.watch(SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE); + + const onSubmit = async (values: SecretFormValues) => { + try { + if (currentProject) { + await postSecretTrigger({ + requestSecretCreate: { + project_id: Number(currentProject), + name: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_NAME], + type: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE], + value: getSecretBodyFromValues(values), + }, + }).unwrap(); + methods.reset(); + toast.success( + t('secretSuccessfullyCreated', { + ns: 'toasts', + secretName: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_NAME], + }), + ); + setIsModalOpen(false); + } + } catch (e) { + handleRequestErrorCatch(e); + } + }; + + const { isValid, isSubmitting } = methods.formState; + + return ( + <> + + + +
+ + + + {t('addSecret', { ns: 'settings' })} + + + {t('secretType', { ns: 'settings' })} + ( + + )} + /> + + + {t('secretName', { ns: 'settings' })}* + ( + + )} + /> + + {watchType ? ( + + + } + loading={isSubmitting || postSecretTriggerState.isLoading}> + {t('addSecret')} + + + ) : null} + + +
+
+
+ + ); +}; + +export default SettingsAddSecret; diff --git a/console/ui/src/features/bradcrumbs/hooks/useBreadcrumbs.tsx b/console/ui/src/features/bradcrumbs/hooks/useBreadcrumbs.tsx new file mode 100644 index 000000000..b322dbf3f --- /dev/null +++ b/console/ui/src/features/bradcrumbs/hooks/useBreadcrumbs.tsx @@ -0,0 +1,19 @@ +import { useMatches } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + +const useBreadcrumbs = (): { label: string; path: string }[] => { + const { t } = useTranslation(); + const matches = useMatches(); + + return matches + .filter((match: any) => Boolean(match?.handle?.breadcrumb)) + .map((match) => ({ + label: + typeof match.handle.breadcrumb.label === 'function' + ? match.handle.breadcrumb.label({ ...match.params }) + : t(match.handle.breadcrumb.label, { ns: match.handle.breadcrumb.ns }), + path: match.handle.breadcrumb?.path ?? match.pathname, + })); +}; + +export default useBreadcrumbs; diff --git a/console/ui/src/features/bradcrumbs/index.ts b/console/ui/src/features/bradcrumbs/index.ts new file mode 100644 index 000000000..3c2d1b2b0 --- /dev/null +++ b/console/ui/src/features/bradcrumbs/index.ts @@ -0,0 +1,3 @@ +import Breadcrumbs from '@/features/bradcrumbs/ui'; + +export default Breadcrumbs; diff --git a/console/ui/src/features/bradcrumbs/ui/index.tsx b/console/ui/src/features/bradcrumbs/ui/index.tsx new file mode 100644 index 000000000..a8536c440 --- /dev/null +++ b/console/ui/src/features/bradcrumbs/ui/index.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react'; +import BreadcrumbsItem from '@entities/breadcumb-item'; +import useBreadcrumbs from '@/features/bradcrumbs/hooks/useBreadcrumbs.tsx'; +import { Breadcrumbs as MaterialBreadcrumbs, Icon, Link, Typography } from '@mui/material'; +import RouterPaths from '@app/router/routerPathsConfig'; +import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; + +const Breadcrumbs: FC = () => { + const breadcrumbs = useBreadcrumbs(); + + return ( + + + + + + + {breadcrumbs.map((breadcrumb, index) => + index === breadcrumbs.length - 1 ? ( + + {breadcrumb.label} + + ) : ( + + ), + )} + + ); +}; + +export default Breadcrumbs; diff --git a/console/ui/src/features/cluster-secret-modal/index.ts b/console/ui/src/features/cluster-secret-modal/index.ts new file mode 100644 index 000000000..1e2556575 --- /dev/null +++ b/console/ui/src/features/cluster-secret-modal/index.ts @@ -0,0 +1,3 @@ +import ClusterSecretModal from '@features/cluster-secret-modal/ui'; + +export default ClusterSecretModal; diff --git a/console/ui/src/features/cluster-secret-modal/lib/functions.ts b/console/ui/src/features/cluster-secret-modal/lib/functions.ts new file mode 100644 index 000000000..5e2864857 --- /dev/null +++ b/console/ui/src/features/cluster-secret-modal/lib/functions.ts @@ -0,0 +1,169 @@ +import { RequestClusterCreate } from '@shared/api/api/clusters.ts'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { PROVIDER_CODE_TO_ANSIBLE_USER_MAP } from '@features/cluster-secret-modal/model/constants.ts'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; +import { PROVIDERS } from '@shared/config/constants.ts'; +import { ClusterFormValues } from '@features/cluster-secret-modal/model/types.ts'; + +import { + SECRET_MODAL_CONTENT_BODY_FORM_FIELDS, + SECRET_MODAL_CONTENT_FORM_FIELD_NAMES, +} from '@entities/secret-form-block/model/constants.ts'; + +export const getCommonExtraVars = (values: ClusterFormValues) => ({ + postgresql_version: values[CLUSTER_FORM_FIELD_NAMES.POSTGRES_VERSION], + patroni_cluster_name: values[CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME], +}); + +export const getCloudProviderExtraVars = (values: ClusterFormValues) => ({ + cloud_provider: values[CLUSTER_FORM_FIELD_NAMES.PROVIDER].code, + server_type: values[CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG].code, + server_location: values[CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG].code, + server_count: values[CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT], + volume_size: values[CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT], + ssh_public_keys: values[CLUSTER_FORM_FIELD_NAMES.SSH_PUBLIC_KEY].split('\n').map((key) => `'${key}'`), + ansible_user: PROVIDER_CODE_TO_ANSIBLE_USER_MAP[values[CLUSTER_FORM_FIELD_NAMES.PROVIDER].code], + ...getCommonExtraVars(values), + ...values[CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG].cloud_image.image, +}); + +export const getLocalMachineExtraVars = (values: ClusterFormValues, secretId?: number) => ({ + ...(values[CLUSTER_FORM_FIELD_NAMES.CLUSTER_VIP_ADDRESS] + ? { cluster_vip: values[CLUSTER_FORM_FIELD_NAMES.CLUSTER_VIP_ADDRESS] } + : {}), + ...(values[CLUSTER_FORM_FIELD_NAMES.IS_HAPROXY_LOAD_BALANCER] ? { with_haproxy_load_balancing: true } : {}), + ...(!secretId && + !values[CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET] && + values[CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD] === AUTHENTICATION_METHODS.PASSWORD + ? { + ansible_user: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME], + ansible_ssh_pass: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD], + } + : {}), + ...getCommonExtraVars(values), +}); + +export const getLocalMachineEnvs = (values: ClusterFormValues, secretId?: number) => ({ + ...(values[CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD] === AUTHENTICATION_METHODS.SSH && + !values[CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET] && + !secretId + ? { + SSH_PRIVATE_KEY_CONTENT: btoa(values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SSH_PRIVATE_KEY]), + } + : {}), + ANSIBLE_INVENTORY_JSON: btoa( + JSON.stringify({ + all: { + vars: { + ansible_user: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME], + ...(values[CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD] === AUTHENTICATION_METHODS.PASSWORD + ? { + ansible_ssh_pass: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME], + ansible_sudo_pass: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD], + } + : {}), + }, + children: { + balancers: { + hosts: values[CLUSTER_FORM_FIELD_NAMES.IS_HAPROXY_LOAD_BALANCER] + ? values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS].reduce( + (acc, server) => ({ + ...acc, + [server[CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS]]: { + ansible_host: server[CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS], + }, + }), + {}, + ) + : {}, + }, + consul_instances: { + hosts: {}, + }, + etcd_cluster: { + hosts: values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS].reduce( + (acc, server) => ({ + ...acc, + [server[CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS]]: { + ansible_host: server[CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS], + }, + }), + {}, + ), + }, + master: { + hosts: { + [values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS][0][CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS]]: { + hostname: values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS][0][CLUSTER_FORM_FIELD_NAMES.HOSTNAME], + ansible_host: values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS][0][CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS], + server_location: + values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]?.[0]?.[CLUSTER_FORM_FIELD_NAMES.LOCATION], + postgresql_exists: false, + }, + }, + }, + ...(values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS].length > 1 + ? { + replica: { + hosts: values[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS].slice(1).reduce( + (acc, server) => ({ + ...acc, + [server.ipAddress]: { + hostname: server?.[CLUSTER_FORM_FIELD_NAMES.HOSTNAME], + ansible_host: server?.[CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS], + server_location: server?.[CLUSTER_FORM_FIELD_NAMES.LOCATION], + postgresql_exists: false, + }, + }), + {}, + ), + }, + } + : {}), + postgres_cluster: { + children: { + master: {}, + replica: {}, + }, + }, + }, + }, + }), + ), +}); + +const convertObjectToRequiredFormat = (object: Record) => { + return Object.entries(object).reduce((acc: string[], [key, value]) => [...acc, `${key}=${value}`], []); +}; + +export const mapFormValuesToRequestFields = ({ + values, + secretId, + projectId, + envs, +}: { + values: ClusterFormValues; + secretId?: number; + projectId: number; + envs?: object; +}): RequestClusterCreate => ({ + project_id: projectId, + name: values[CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME], + environment_id: values[CLUSTER_FORM_FIELD_NAMES.ENVIRONMENT_ID], + description: values[CLUSTER_FORM_FIELD_NAMES.DESCRIPTION], + ...(secretId ? { auth_info: { secret_id: secretId } } : {}), + ...(values[CLUSTER_FORM_FIELD_NAMES.PROVIDER].code === PROVIDERS.LOCAL + ? { envs: convertObjectToRequiredFormat(getLocalMachineEnvs(values, secretId)) } + : envs && values[CLUSTER_FORM_FIELD_NAMES.PROVIDER].code !== PROVIDERS.LOCAL + ? { + envs: convertObjectToRequiredFormat( + Object.fromEntries(Object.entries(envs).filter(([key]) => SECRET_MODAL_CONTENT_BODY_FORM_FIELDS?.[key])), + ), + } + : {}), + extra_vars: convertObjectToRequiredFormat( + values[CLUSTER_FORM_FIELD_NAMES.PROVIDER].code === PROVIDERS.LOCAL + ? getLocalMachineExtraVars(values, secretId) + : getCloudProviderExtraVars(values), + ), +}); diff --git a/console/ui/src/features/cluster-secret-modal/model/constants.ts b/console/ui/src/features/cluster-secret-modal/model/constants.ts new file mode 100644 index 000000000..04ee1d8df --- /dev/null +++ b/console/ui/src/features/cluster-secret-modal/model/constants.ts @@ -0,0 +1,16 @@ +import { PROVIDERS } from '@shared/config/constants.ts'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +export const CLUSTER_SECRET_MODAL_FORM_FIELD_NAMES = Object.freeze({ + ...SECRET_MODAL_CONTENT_FORM_FIELD_NAMES, + IS_SAVE_TO_CONSOLE: 'isSaveToConsole', +}); + +export const PROVIDER_CODE_TO_ANSIBLE_USER_MAP = Object.freeze({ + [PROVIDERS.AWS]: 'ubuntu', + [PROVIDERS.GCP]: 'root', + [PROVIDERS.AZURE]: 'azureadmin', + [PROVIDERS.DIGITAL_OCEAN]: 'root', + [PROVIDERS.HETZNER]: 'root', +}); diff --git a/console/ui/src/features/cluster-secret-modal/model/types.ts b/console/ui/src/features/cluster-secret-modal/model/types.ts new file mode 100644 index 000000000..48f055a98 --- /dev/null +++ b/console/ui/src/features/cluster-secret-modal/model/types.ts @@ -0,0 +1,53 @@ +import { CLUSTER_SECRET_MODAL_FORM_FIELD_NAMES } from '@features/cluster-secret-modal/model/constants.ts'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { + DeploymentInfoCloudRegion, + DeploymentInstanceType, + ResponseDeploymentInfo, +} from '@shared/api/api/deployments.ts'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; +import { ClusterDatabaseServer } from '@widgets/cluster-form/model/types.ts'; +import { SecretFormValues } from '@entities/secret-form-block/model/types.ts'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +export interface ClusterSecretModalProps { + isClusterFormSubmitting?: boolean; + isClusterFormDisabled?: boolean; +} + +export interface ClusterSecretModalFormValues extends SecretFormValues { + [CLUSTER_SECRET_MODAL_FORM_FIELD_NAMES.IS_SAVE_TO_CONSOLE]: boolean; +} + +interface ClusterCloudProviderFormValues { + [CLUSTER_FORM_FIELD_NAMES.REGION]?: string; + [CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG]?: DeploymentInfoCloudRegion; + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_TYPE]?: ['small', 'medium', 'large']; + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]?: DeploymentInstanceType; + [CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT]?: number; + [CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT]?: number; + [CLUSTER_FORM_FIELD_NAMES.SSH_PUBLIC_KEY]?: string; +} + +interface ClusterLocalMachineProviderFormValues + extends Pick< + SECRET_MODAL_CONTENT_FORM_FIELD_NAMES, + | SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME + | SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD + | SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PRIVATE_KEY + > { + [CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]?: ClusterDatabaseServer[]; + [CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD]?: typeof AUTHENTICATION_METHODS; + [CLUSTER_FORM_FIELD_NAMES.SECRET_KEY_NAME]?: string; + [CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_IS_SAVE_TO_CONSOLE]?: boolean; + [CLUSTER_FORM_FIELD_NAMES.CLUSTER_VIP_ADDRESS]?: string; + [CLUSTER_FORM_FIELD_NAMES.IS_HAPROXY_LOAD_BALANCER]?: boolean; +} + +export interface ClusterFormValues extends ClusterCloudProviderFormValues, ClusterLocalMachineProviderFormValues { + [CLUSTER_FORM_FIELD_NAMES.PROVIDER]: ResponseDeploymentInfo; + [CLUSTER_FORM_FIELD_NAMES.ENVIRONMENT_ID]: number; + [CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME]: string; + [CLUSTER_FORM_FIELD_NAMES.DESCRIPTION]: string; + [CLUSTER_FORM_FIELD_NAMES.POSTGRES_VERSION]: number; +} diff --git a/console/ui/src/features/cluster-secret-modal/model/validation.ts b/console/ui/src/features/cluster-secret-modal/model/validation.ts new file mode 100644 index 000000000..e69de29bb diff --git a/console/ui/src/features/cluster-secret-modal/ui/index.tsx b/console/ui/src/features/cluster-secret-modal/ui/index.tsx new file mode 100644 index 000000000..1bfeb6804 --- /dev/null +++ b/console/ui/src/features/cluster-secret-modal/ui/index.tsx @@ -0,0 +1,219 @@ +import { FC, useRef, useState } from 'react'; +import { + Box, + Button, + Card, + Checkbox, + CircularProgress, + FormControlLabel, + MenuItem, + Modal, + Stack, + TextField, +} from '@mui/material'; +import { Controller, FormProvider, useForm, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { generateAbsoluteRouterPath, handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { useNavigate } from 'react-router-dom'; +import { ClusterSecretModalFormValues, ClusterSecretModalProps } from '@features/cluster-secret-modal/model/types.ts'; +import { LoadingButton } from '@mui/lab'; +import { useGetSecretsQuery, usePostSecretsMutation } from '@shared/api/api/secrets.ts'; +import { CLUSTER_SECRET_MODAL_FORM_FIELD_NAMES } from '@features/cluster-secret-modal/model/constants.ts'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import { toast } from 'react-toastify'; +import { mapFormValuesToRequestFields } from '@features/cluster-secret-modal/lib/functions.ts'; +import { usePostClustersMutation } from '@shared/api/api/clusters.ts'; +import SecretFormBlock from '@entities/secret-form-block'; + +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; +import { getSecretBodyFromValues } from '@entities/secret-form-block/lib/functions.ts'; + +const ClusterSecretModal: FC = ({ isClusterFormDisabled = false }) => { + const { t } = useTranslation(['clusters', 'shared', 'toasts']); + const navigate = useNavigate(); + const createSecretResultRef = useRef(null); // ref is used for case when user saves secret and uses its ID to create cluster + + const currentProject = useAppSelector(selectCurrentProject); + + const [isModalOpen, setIsModalOpen] = useState(false); + + const { watch, getValues } = useFormContext(); + + const watchProvider = watch(CLUSTER_FORM_FIELD_NAMES.PROVIDER); + + const secrets = useGetSecretsQuery({ type: watchProvider?.code, projectId: currentProject }); + + const [addSecretTrigger, addSecretTriggerState] = usePostSecretsMutation(); + const [addClusterTrigger, addClusterTriggerState] = usePostClustersMutation(); + + const methods = useForm(); + + const watchIsSaveToConsole = methods.watch(CLUSTER_SECRET_MODAL_FORM_FIELD_NAMES.IS_SAVE_TO_CONSOLE); + + const handleModalOpenState = (isOpen: boolean) => () => setIsModalOpen(isOpen); + + const cancelHandler = () => navigate(generateAbsoluteRouterPath(RouterPaths.clusters.absolutePath)); + + const onSubmit = async (values: ClusterSecretModalFormValues) => { + const clusterFormValues = getValues(); + try { + if (values[CLUSTER_SECRET_MODAL_FORM_FIELD_NAMES.IS_SAVE_TO_CONSOLE] && !createSecretResultRef?.current) { + createSecretResultRef.current = await addSecretTrigger({ + requestSecretCreate: { + project_id: Number(currentProject), + type: clusterFormValues[CLUSTER_FORM_FIELD_NAMES.PROVIDER].code, + name: values[CLUSTER_SECRET_MODAL_FORM_FIELD_NAMES.SECRET_NAME], + value: getSecretBodyFromValues({ + ...values, + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE]: clusterFormValues.provider.code, + }), + }, + }).unwrap(); + toast.success( + t('secretSuccessfullyCreated', { + ns: 'toasts', + secretName: values[SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_NAME], + }), + ); + } + if (!secrets.data?.data?.length && !createSecretResultRef?.current?.id) { + await addClusterTrigger({ + requestClusterCreate: mapFormValuesToRequestFields({ + values: clusterFormValues, + envs: values, + projectId: Number(currentProject), + }), + }).unwrap(); + } else { + await addClusterTrigger({ + requestClusterCreate: mapFormValuesToRequestFields({ + values: clusterFormValues, + secretId: createSecretResultRef.current?.id ?? values[CLUSTER_FORM_FIELD_NAMES.SECRET_ID], + projectId: Number(currentProject), + }), + }).unwrap(); + } + toast.success( + t('clusterSuccessfullyCreated', { + ns: 'toasts', + clusterName: clusterFormValues[CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME], + }), + ); + navigate(generateAbsoluteRouterPath(RouterPaths.clusters.absolutePath)); + } catch (e) { + handleRequestErrorCatch(e); + } finally { + setIsModalOpen(false); + } + }; + + const { isValid, isDirty, isSubmitting } = methods.formState; + + return ( + + + {t('createCluster', { ns: 'clusters' })} + + + + +
+ + + {secrets.data?.data?.length > 1 ? ( + ( + + {secrets.data.data.map((secret) => ( + + {secret?.name} + + ))} + + )} + /> + ) : ( + <> + + {watchIsSaveToConsole ? ( + ( + + )} + /> + ) : null} + ( + } + checked={value} + onChange={onChange} + label={t('saveToConsole', { ns: 'clusters' })} + /> + )} + /> + + )} + } + fullWidth={false}> + {t('createCluster', { ns: 'clusters' })} + + + +
+
+
+
+ +
+ ); +}; + +export default ClusterSecretModal; diff --git a/console/ui/src/features/clusters-overview-table-row-actions/index.ts b/console/ui/src/features/clusters-overview-table-row-actions/index.ts new file mode 100644 index 000000000..11ec5d22e --- /dev/null +++ b/console/ui/src/features/clusters-overview-table-row-actions/index.ts @@ -0,0 +1,3 @@ +import ClustersOverviewTableRowActions from '@features/clusters-overview-table-row-actions/ui'; + +export default ClustersOverviewTableRowActions; diff --git a/console/ui/src/features/clusters-overview-table-row-actions/ui/index.tsx b/console/ui/src/features/clusters-overview-table-row-actions/ui/index.tsx new file mode 100644 index 000000000..624ec646f --- /dev/null +++ b/console/ui/src/features/clusters-overview-table-row-actions/ui/index.tsx @@ -0,0 +1,47 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import { ListItemIcon, MenuItem } from '@mui/material'; +import { TableRowActionsProps } from '@shared/model/types.ts'; +import { toast } from 'react-toastify'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import { useDeleteServersByIdMutation } from '@shared/api/api/other.ts'; +import { CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES } from '@widgets/cluster-overview-table/model/constants.ts'; +import { useLazyGetClustersByIdQuery } from '@shared/api/api/clusters.ts'; +import { useParams } from 'react-router-dom'; + +const ClustersOverviewTableRowActions: FC = ({ closeMenu, row }) => { + const { t } = useTranslation(['shared', 'toasts']); + const { clusterId } = useParams(); + + const [removeServerTrigger] = useDeleteServersByIdMutation(); + const [getClusterTrigger] = useLazyGetClustersByIdQuery(); + + const handleButtonClick = async () => { + try { + await removeServerTrigger({ id: row.original[CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.ID] }).unwrap(); + toast.success( + t('serverSuccessfullyRemoved', { + ns: 'toasts', + serverName: row.original[CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.NAME], + }), + ); + await getClusterTrigger({ id: clusterId }); + } catch (e) { + handleRequestErrorCatch(e); + } finally { + closeMenu(); + } + }; + + return [ + + + + + {t('removeFromList', { ns: 'shared' })} + , + ]; +}; + +export default ClustersOverviewTableRowActions; diff --git a/console/ui/src/features/clusters-table-buttons/index.ts b/console/ui/src/features/clusters-table-buttons/index.ts new file mode 100644 index 000000000..d8302b69b --- /dev/null +++ b/console/ui/src/features/clusters-table-buttons/index.ts @@ -0,0 +1,3 @@ +import ClustersTableButtons from '@features/clusters-table-buttons/ui'; + +export default ClustersTableButtons; diff --git a/console/ui/src/features/clusters-table-buttons/model/types.ts b/console/ui/src/features/clusters-table-buttons/model/types.ts new file mode 100644 index 000000000..ed84d60ba --- /dev/null +++ b/console/ui/src/features/clusters-table-buttons/model/types.ts @@ -0,0 +1,3 @@ +export interface ClustersTableButtonsProps { + refetch: () => void; +} diff --git a/console/ui/src/features/clusters-table-buttons/ui/index.tsx b/console/ui/src/features/clusters-table-buttons/ui/index.tsx new file mode 100644 index 000000000..3d12a84ac --- /dev/null +++ b/console/ui/src/features/clusters-table-buttons/ui/index.tsx @@ -0,0 +1,35 @@ +import { useTranslation } from 'react-i18next'; +import { Button, Stack } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; +import RouterPaths from '@app/router/routerPathsConfig'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import AddIcon from '@mui/icons-material/Add'; +import { ClustersTableButtonsProps } from '@features/clusters-table-buttons/model/types.ts'; +import { FC } from 'react'; + +const ClustersTableButtons: FC = ({ refetch }) => { + const { t } = useTranslation(['clusters, shared']); + const navigate = useNavigate(); + + const handleRefresh = () => { + refetch(); + }; + + const handleCreateCluster = () => { + navigate(generateAbsoluteRouterPath(RouterPaths.clusters.add.absolutePath)); + }; + + return ( + + + + + ); +}; + +export default ClustersTableButtons; diff --git a/console/ui/src/features/clusters-table-row-actions/index.ts b/console/ui/src/features/clusters-table-row-actions/index.ts new file mode 100644 index 000000000..84a49ed2d --- /dev/null +++ b/console/ui/src/features/clusters-table-row-actions/index.ts @@ -0,0 +1,3 @@ +import ClustersTableRowActions from '@features/clusters-table-row-actions/ui'; + +export default ClustersTableRowActions; diff --git a/console/ui/src/features/clusters-table-row-actions/model/types.ts b/console/ui/src/features/clusters-table-row-actions/model/types.ts new file mode 100644 index 000000000..84135902e --- /dev/null +++ b/console/ui/src/features/clusters-table-row-actions/model/types.ts @@ -0,0 +1,5 @@ +export interface ClustersTableRemoveButtonProps { + clusterId: number; + clusterName: string; + closeMenu: () => void; +} diff --git a/console/ui/src/features/clusters-table-row-actions/ui/ClusterTableRemoveButton.tsx b/console/ui/src/features/clusters-table-row-actions/ui/ClusterTableRemoveButton.tsx new file mode 100644 index 000000000..a168dc9be --- /dev/null +++ b/console/ui/src/features/clusters-table-row-actions/ui/ClusterTableRemoveButton.tsx @@ -0,0 +1,71 @@ +import { FC, useState } from 'react'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import { + Button, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Stack, + Typography, +} from '@mui/material'; +import { LoadingButton } from '@mui/lab'; +import { useTranslation } from 'react-i18next'; +import { ClustersTableRemoveButtonProps } from '@features/clusters-table-row-actions/model/types.ts'; +import { useDeleteClustersByIdMutation } from '@shared/api/api/clusters.ts'; +import { toast } from 'react-toastify'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; + +const ClustersTableRemoveButton: FC = ({ clusterId, clusterName, closeMenu }) => { + const { t } = useTranslation(['clusters', 'shared']); + + const [isModalOpen, setIsModalOpen] = useState(false); + + const [removeClusterTrigger, removeClusterTriggerState] = useDeleteClustersByIdMutation(); + + const handleModalOpenState = (state: boolean) => () => { + setIsModalOpen(state); + if (!state) closeMenu(); + }; + + const handleButtonClick = async () => { + try { + await removeClusterTrigger({ id: clusterId }); + closeMenu(); + toast.success(t('clusterSuccessfullyRemoved', { ns: 'toasts', clusterName })); + } catch (e) { + handleRequestErrorCatch(e); + } + }; + + return ( + <> + + + {t('deleteClusterModalHeader', { ns: 'clusters', clusterName })} + + {t('deleteClusterModalBody', { ns: 'clusters', clusterName })} + + + + } + loading={removeClusterTriggerState.isLoading}> + {t('delete', { ns: 'shared' })} + + + + + ); +}; + +export default ClustersTableRemoveButton; diff --git a/console/ui/src/features/clusters-table-row-actions/ui/index.tsx b/console/ui/src/features/clusters-table-row-actions/ui/index.tsx new file mode 100644 index 000000000..4fcfda388 --- /dev/null +++ b/console/ui/src/features/clusters-table-row-actions/ui/index.tsx @@ -0,0 +1,14 @@ +import { FC } from 'react'; +import { TableRowActionsProps } from '@shared/model/types.ts'; +import ClustersTableRemoveButton from '@features/clusters-table-row-actions/ui/ClusterTableRemoveButton.tsx'; + +const ClustersTableRowActions: FC = ({ closeMenu, row }) => [ + , +]; + +export default ClustersTableRowActions; diff --git a/console/ui/src/features/environments-table-row-actions/ui/index.tsx b/console/ui/src/features/environments-table-row-actions/ui/index.tsx new file mode 100644 index 000000000..1e7bbad94 --- /dev/null +++ b/console/ui/src/features/environments-table-row-actions/ui/index.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react'; +import { TableRowActionsProps } from '@shared/model/types.ts'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import { ListItemIcon, MenuItem } from '@mui/material'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import { useDeleteEnvironmentsByIdMutation } from '@shared/api/api/environments.ts'; +import { ENVIRONMENTS_TABLE_COLUMN_NAMES } from '@widgets/environments-table/model/constants.ts'; + +const EnvironmentsTableRowActions: FC = ({ closeMenu, row }) => { + const { t } = useTranslation(['shared', 'toasts']); + + const [removeEnvironmentTrigger] = useDeleteEnvironmentsByIdMutation(); + + const handleButtonClick = async () => { + try { + await removeEnvironmentTrigger({ id: row.original[ENVIRONMENTS_TABLE_COLUMN_NAMES.ID] }).unwrap(); + toast.success( + t('environmentSuccessfullyRemoved', { + ns: 'toasts', + environmentName: row.original[ENVIRONMENTS_TABLE_COLUMN_NAMES.NAME], + }), + ); + } catch (e) { + handleRequestErrorCatch(e); + } finally { + closeMenu(); + } + }; + + return [ + + + + + {t('delete')} + , + ]; +}; + +export default EnvironmentsTableRowActions; diff --git a/console/ui/src/features/logout-button/index.ts b/console/ui/src/features/logout-button/index.ts new file mode 100644 index 000000000..5940e264f --- /dev/null +++ b/console/ui/src/features/logout-button/index.ts @@ -0,0 +1,3 @@ +import LogoutButton from '@features/logout-button/ui'; + +export default LogoutButton; diff --git a/console/ui/src/features/logout-button/ui/index.tsx b/console/ui/src/features/logout-button/ui/index.tsx new file mode 100644 index 000000000..1c6d206d7 --- /dev/null +++ b/console/ui/src/features/logout-button/ui/index.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; +import Logout from '@shared/assets/logoutIcon.svg?react'; +import { Icon } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; + +const LogoutButton: FC = () => { + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem('token'); + navigate(generateAbsoluteRouterPath(RouterPaths.login.absolutePath)); + }; + + return ( + + + + ); +}; + +export default LogoutButton; diff --git a/console/ui/src/features/operations-table-buttons/index.ts b/console/ui/src/features/operations-table-buttons/index.ts new file mode 100644 index 000000000..1e48e3d8c --- /dev/null +++ b/console/ui/src/features/operations-table-buttons/index.ts @@ -0,0 +1,3 @@ +import OperationsTableButtons from '@features/operations-table-buttons/ui'; + +export default OperationsTableButtons; diff --git a/console/ui/src/features/operations-table-buttons/lib/functions.ts b/console/ui/src/features/operations-table-buttons/lib/functions.ts new file mode 100644 index 000000000..d342c7c15 --- /dev/null +++ b/console/ui/src/features/operations-table-buttons/lib/functions.ts @@ -0,0 +1,61 @@ +import { startOfDay } from 'date-fns/startOfDay'; +import { subDays } from 'date-fns/subDays'; +import { subMonths } from 'date-fns/subMonths'; +import { subYears } from 'date-fns/subYears'; +import { TFunction } from 'i18next'; +import { DATE_RANGE_VALUES } from '@features/operations-table-buttons/model/constants.ts'; + +export const formatOperationsDate = (date: Date) => startOfDay(date).toISOString(); + +export const getOperationsTimeNameValue = (name: keyof DATE_RANGE_VALUES) => { + let value = ''; + + switch (name) { + case DATE_RANGE_VALUES.LAST_DAY: + value = formatOperationsDate(subDays(new Date(), 1)); + break; + case DATE_RANGE_VALUES.LAST_WEEK: + value = formatOperationsDate(subDays(new Date(), 7)); + break; + case DATE_RANGE_VALUES.LAST_MONTH: + value = formatOperationsDate(subMonths(new Date(), 1)); + break; + case DATE_RANGE_VALUES.LAST_THREE_MONTHS: + value = formatOperationsDate(subMonths(new Date(), 3)); + break; + case DATE_RANGE_VALUES.LAST_SIX_MONTHS: + value = formatOperationsDate(subMonths(new Date(), 6)); + break; + case DATE_RANGE_VALUES.LAST_YEAR: + value = formatOperationsDate(subYears(new Date(), 1)); + break; + } + return { name, value }; +}; + +export const getOperationsDateRangeVariants = (t: TFunction) => [ + { + label: t('lastDay', { ns: 'operations' }), + value: DATE_RANGE_VALUES.LAST_DAY, + }, + { + label: t('lastWeek', { ns: 'operations' }), + value: DATE_RANGE_VALUES.LAST_WEEK, + }, + { + label: t('lastMonth', { ns: 'operations' }), + value: DATE_RANGE_VALUES.LAST_MONTH, + }, + { + label: t('lastThreeMonths', { ns: 'operations' }), + value: DATE_RANGE_VALUES.LAST_THREE_MONTHS, + }, + { + label: t('lastSixMonths', { ns: 'operations' }), + value: DATE_RANGE_VALUES.LAST_SIX_MONTHS, + }, + { + label: t('lastYear', { ns: 'operations' }), + value: DATE_RANGE_VALUES.LAST_YEAR, + }, +]; diff --git a/console/ui/src/features/operations-table-buttons/model/constants.ts b/console/ui/src/features/operations-table-buttons/model/constants.ts new file mode 100644 index 000000000..091945239 --- /dev/null +++ b/console/ui/src/features/operations-table-buttons/model/constants.ts @@ -0,0 +1,8 @@ +export const DATE_RANGE_VALUES = Object.freeze({ + LAST_DAY: 'lastDay', + LAST_WEEK: 'lastWeek', + LAST_MONTH: 'lastMonth', + LAST_THREE_MONTHS: 'lastThreeMonths', + LAST_SIX_MONTHS: 'lastSixMonths', + LAST_YEAR: 'lastYear', +}); diff --git a/console/ui/src/features/operations-table-buttons/model/types.ts b/console/ui/src/features/operations-table-buttons/model/types.ts new file mode 100644 index 000000000..c29b36eaf --- /dev/null +++ b/console/ui/src/features/operations-table-buttons/model/types.ts @@ -0,0 +1,5 @@ +export interface OperationsTableButtonsProps { + refetch: () => void; + startDate: Date; + setStartDate: (date: Date) => void; +} diff --git a/console/ui/src/features/operations-table-buttons/ui/index.tsx b/console/ui/src/features/operations-table-buttons/ui/index.tsx new file mode 100644 index 000000000..06e55fe71 --- /dev/null +++ b/console/ui/src/features/operations-table-buttons/ui/index.tsx @@ -0,0 +1,53 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, InputAdornment, MenuItem, Stack, TextField } from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import { OperationsTableButtonsProps } from '@features/operations-table-buttons/model/types.ts'; +import CalendarClockIcon from '@shared/assets/calendarClockICon.svg?react'; +import { + getOperationsDateRangeVariants, + getOperationsTimeNameValue, +} from '@features/operations-table-buttons/lib/functions.ts'; + +const OperationsTableButtons: FC = ({ refetch, startDate, setStartDate }) => { + const { t } = useTranslation('operations'); + + const rangeOptions = getOperationsDateRangeVariants(t); + + const handleChange = (e) => { + setStartDate(getOperationsTimeNameValue(e.target.value)); + }; + + const handleRefresh = () => { + refetch(); + }; + + return ( + + + + + ), + }}> + {rangeOptions.map((option) => ( + + {option.label} + + ))} + + + + ); +}; + +export default OperationsTableButtons; diff --git a/console/ui/src/features/operations-table-row-actions/index.ts b/console/ui/src/features/operations-table-row-actions/index.ts new file mode 100644 index 000000000..cbce8e88f --- /dev/null +++ b/console/ui/src/features/operations-table-row-actions/index.ts @@ -0,0 +1,3 @@ +import OperationsTableRowActions from '@features/operations-table-row-actions/ui'; + +export default OperationsTableRowActions; diff --git a/console/ui/src/features/operations-table-row-actions/ui/index.tsx b/console/ui/src/features/operations-table-row-actions/ui/index.tsx new file mode 100644 index 000000000..0fdca6667 --- /dev/null +++ b/console/ui/src/features/operations-table-row-actions/ui/index.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { MenuItem } from '@mui/material'; +import { TableRowActionsProps } from '@shared/model/types.ts'; +import { useNavigate } from 'react-router-dom'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; + +const OperationsTableRowActions: FC = ({ closeMenu, row }) => { + const { t } = useTranslation('operations'); + const navigate = useNavigate(); + + const handleButtonClick = () => { + navigate(generateAbsoluteRouterPath(RouterPaths.operations.log.absolutePath, { operationId: row.original.id })); + closeMenu(); + }; + + return [ + + {t('showDetails')} + , + ]; +}; + +export default OperationsTableRowActions; diff --git a/console/ui/src/features/pojects-table-row-actions/index.ts b/console/ui/src/features/pojects-table-row-actions/index.ts new file mode 100644 index 000000000..8c4e9a64d --- /dev/null +++ b/console/ui/src/features/pojects-table-row-actions/index.ts @@ -0,0 +1,3 @@ +import ProjectsTableRowActions from '@features/pojects-table-row-actions/ui'; + +export default ProjectsTableRowActions; diff --git a/console/ui/src/features/pojects-table-row-actions/ui/index.tsx b/console/ui/src/features/pojects-table-row-actions/ui/index.tsx new file mode 100644 index 000000000..6cffc382d --- /dev/null +++ b/console/ui/src/features/pojects-table-row-actions/ui/index.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react'; +import { ListItemIcon, MenuItem } from '@mui/material'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import { useTranslation } from 'react-i18next'; +import { useDeleteProjectsByIdMutation } from '@shared/api/api/projects.ts'; +import { toast } from 'react-toastify'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import { TableRowActionsProps } from '@shared/model/types.ts'; +import { PROJECTS_TABLE_COLUMN_NAMES } from '@widgets/projects-table/model/constants.ts'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; + +const ProjectsTableRowActions: FC = ({ closeMenu, row }) => { + const { t } = useTranslation(['shared', 'toasts']); + + const currentProject = useAppSelector(selectCurrentProject); + + const [removeProjectTrigger] = useDeleteProjectsByIdMutation(); + + const handleButtonClick = async () => { + try { + if (Number(currentProject) === row.original[PROJECTS_TABLE_COLUMN_NAMES.ID]) + throw t('cannotRemoveActiveProject', { ns: 'toasts' }); + await removeProjectTrigger({ id: row.original[PROJECTS_TABLE_COLUMN_NAMES.ID] }).unwrap(); + toast.success( + t('projectSuccessfullyRemoved', { + ns: 'toasts', + projectName: row.original[PROJECTS_TABLE_COLUMN_NAMES.NAME], + }), + ); + } catch (e) { + handleRequestErrorCatch(e); + } finally { + closeMenu(); + } + }; + + return [ + + + + + {t('delete')} + , + ]; +}; + +export default ProjectsTableRowActions; diff --git a/console/ui/src/features/settings-table-buttons/index.ts b/console/ui/src/features/settings-table-buttons/index.ts new file mode 100644 index 000000000..aadca66d8 --- /dev/null +++ b/console/ui/src/features/settings-table-buttons/index.ts @@ -0,0 +1,3 @@ +import SettingsTableButtons from '@features/settings-table-buttons/ui'; + +export default SettingsTableButtons; diff --git a/console/ui/src/features/settings-table-buttons/lib/functions.ts b/console/ui/src/features/settings-table-buttons/lib/functions.ts new file mode 100644 index 000000000..906dd06ef --- /dev/null +++ b/console/ui/src/features/settings-table-buttons/lib/functions.ts @@ -0,0 +1,3 @@ +export const handleDelete = () => {}; +export const handleEdit = () => {}; +export const handleAddSecret = () => {}; diff --git a/console/ui/src/features/settings-table-buttons/ui/index.tsx b/console/ui/src/features/settings-table-buttons/ui/index.tsx new file mode 100644 index 000000000..f451c805c --- /dev/null +++ b/console/ui/src/features/settings-table-buttons/ui/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Stack } from '@mui/material'; +import SettingsAddSecret from '@features/add-secret'; + +const SettingsTableButtons: React.FC = () => { + const { t } = useTranslation(['shared', 'settings']); + + return ( + + + + ); +}; + +export default SettingsTableButtons; diff --git a/console/ui/src/features/settings-table-row-actions/index.ts b/console/ui/src/features/settings-table-row-actions/index.ts new file mode 100644 index 000000000..3a966bb65 --- /dev/null +++ b/console/ui/src/features/settings-table-row-actions/index.ts @@ -0,0 +1,3 @@ +import SettingsTableRowActions from '@features/settings-table-row-actions/ui'; + +export default SettingsTableRowActions; diff --git a/console/ui/src/features/settings-table-row-actions/model/constants.ts b/console/ui/src/features/settings-table-row-actions/model/constants.ts new file mode 100644 index 000000000..ceb434bcc --- /dev/null +++ b/console/ui/src/features/settings-table-row-actions/model/constants.ts @@ -0,0 +1 @@ +export const SECRET_TOAST_DISPLAY_CLUSTERS_LIMIT = 10; diff --git a/console/ui/src/features/settings-table-row-actions/ui/index.tsx b/console/ui/src/features/settings-table-row-actions/ui/index.tsx new file mode 100644 index 000000000..dcc4a1423 --- /dev/null +++ b/console/ui/src/features/settings-table-row-actions/ui/index.tsx @@ -0,0 +1,57 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ListItemIcon, MenuItem } from '@mui/material'; +import { useDeleteSecretsByIdMutation } from '@shared/api/api/secrets.ts'; +import { TableRowActionsProps } from '@shared/model/types.ts'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import { toast } from 'react-toastify'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import { SECRETS_TABLE_COLUMN_NAMES } from '@widgets/secrets-table/model/constants.ts'; +import { SECRET_TOAST_DISPLAY_CLUSTERS_LIMIT } from '@features/settings-table-row-actions/model/constants.ts'; + +const SettingsTableRowActions: FC = ({ closeMenu, row }) => { + const { t } = useTranslation(['shared', 'toasts']); + + const [removeSecretTrigger] = useDeleteSecretsByIdMutation(); + + const handleButtonClick = async () => { + try { + if (row.original[SECRETS_TABLE_COLUMN_NAMES.USED].toString() === 'true') { + const usingClusterList = row.original[SECRETS_TABLE_COLUMN_NAMES.USED_BY]?.split(', '); + toast.warning( + t('secretsSecretIsUsed', { + ns: 'toasts', + count: usingClusterList?.length, + clusterNames: + usingClusterList?.length > SECRET_TOAST_DISPLAY_CLUSTERS_LIMIT + ? `${[...usingClusterList.slice(0, SECRET_TOAST_DISPLAY_CLUSTERS_LIMIT), '...'].join(', ')}` + : row.original[SECRETS_TABLE_COLUMN_NAMES.USED_BY], + }), + ); + } else { + await removeSecretTrigger({ id: row.original[SECRETS_TABLE_COLUMN_NAMES.ID] }).unwrap(); + toast.success( + t('secretSuccessfullyRemoved', { + ns: 'toasts', + secretName: row.original[SECRETS_TABLE_COLUMN_NAMES.NAME], + }), + ); + } + } catch (e) { + handleRequestErrorCatch(e); + } finally { + closeMenu(); + } + }; + + return [ + + + + + {t('delete')} + , + ]; +}; + +export default SettingsTableRowActions; diff --git a/console/ui/src/pages/404/index.ts b/console/ui/src/pages/404/index.ts new file mode 100644 index 000000000..29e8d7e6a --- /dev/null +++ b/console/ui/src/pages/404/index.ts @@ -0,0 +1,3 @@ +import Page404 from '@pages/404/ui'; + +export default Page404; diff --git a/console/ui/src/pages/404/ui/illustration.tsx b/console/ui/src/pages/404/ui/illustration.tsx new file mode 100644 index 000000000..00fc35061 --- /dev/null +++ b/console/ui/src/pages/404/ui/illustration.tsx @@ -0,0 +1,14 @@ +import { ComponentPropsWithoutRef } from 'react'; + +const Illustration = (props: ComponentPropsWithoutRef<'svg'>) => { + return ( + + + + ); +}; + +export default Illustration; diff --git a/console/ui/src/pages/404/ui/index.tsx b/console/ui/src/pages/404/ui/index.tsx new file mode 100644 index 000000000..d11064428 --- /dev/null +++ b/console/ui/src/pages/404/ui/index.tsx @@ -0,0 +1,39 @@ +import { FC } from 'react'; +import Illustration from '@pages/404/ui/illustration.tsx'; +import { useTranslation } from 'react-i18next'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; +import { useNavigate } from 'react-router-dom'; +import { Box, Button, Container, Typography } from '@mui/material'; +import theme from '@shared/theme/theme.ts'; +import { grey } from '@mui/material/colors'; + +const Page404: FC = () => { + const { t } = useTranslation('shared'); + const navigate = useNavigate(); + + const handleReturnButton = () => navigate(generateAbsoluteRouterPath(RouterPaths.clusters.absolutePath)); + + return ( + + + + + + {t('404Title')} + + + {t('404Text')} + + + + + + + + ); +}; + +export default Page404; diff --git a/console/ui/src/pages/add-cluster/index.ts b/console/ui/src/pages/add-cluster/index.ts new file mode 100644 index 000000000..b4ea872b3 --- /dev/null +++ b/console/ui/src/pages/add-cluster/index.ts @@ -0,0 +1,3 @@ +import AddCluster from '@pages/add-cluster/ui'; + +export default AddCluster; diff --git a/console/ui/src/pages/add-cluster/ui/index.tsx b/console/ui/src/pages/add-cluster/ui/index.tsx new file mode 100644 index 000000000..102b3724a --- /dev/null +++ b/console/ui/src/pages/add-cluster/ui/index.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import ClusterForm from '@widgets/cluster-form'; + +const AddCluster: FC = () => { + return ; +}; + +export default AddCluster; diff --git a/console/ui/src/pages/clusters/index.ts b/console/ui/src/pages/clusters/index.ts new file mode 100644 index 000000000..f3e8d8643 --- /dev/null +++ b/console/ui/src/pages/clusters/index.ts @@ -0,0 +1,3 @@ +import Clusters from '@pages/clusters/ui'; + +export default Clusters; diff --git a/console/ui/src/pages/clusters/ui/index.tsx b/console/ui/src/pages/clusters/ui/index.tsx new file mode 100644 index 000000000..06daa706b --- /dev/null +++ b/console/ui/src/pages/clusters/ui/index.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { Box } from '@mui/material'; +import ClustersTable from '@widgets/clusters-table'; + +const Clusters: FC = () => { + return ( + + + + ); +}; + +export default Clusters; diff --git a/console/ui/src/pages/login/index.ts b/console/ui/src/pages/login/index.ts new file mode 100644 index 000000000..6895e2a10 --- /dev/null +++ b/console/ui/src/pages/login/index.ts @@ -0,0 +1,3 @@ +import Login from '@pages/login/ui'; + +export default Login; diff --git a/console/ui/src/pages/login/model/constants.ts b/console/ui/src/pages/login/model/constants.ts new file mode 100644 index 000000000..cfeaebc7c --- /dev/null +++ b/console/ui/src/pages/login/model/constants.ts @@ -0,0 +1,3 @@ +export const LOGIN_FORM_FIELD_NAMES = Object.freeze({ + TOKEN: 'token', +}); diff --git a/console/ui/src/pages/login/model/types.ts b/console/ui/src/pages/login/model/types.ts new file mode 100644 index 000000000..50729a4d5 --- /dev/null +++ b/console/ui/src/pages/login/model/types.ts @@ -0,0 +1,5 @@ +import { LOGIN_FORM_FIELD_NAMES } from '@pages/login/model/constants.ts'; + +export interface LoginFormValues { + [LOGIN_FORM_FIELD_NAMES.TOKEN]: string; +} diff --git a/console/ui/src/pages/login/ui/index.tsx b/console/ui/src/pages/login/ui/index.tsx new file mode 100644 index 000000000..a39661378 --- /dev/null +++ b/console/ui/src/pages/login/ui/index.tsx @@ -0,0 +1,75 @@ +import { FC } from 'react'; +import { Box, Button, Link, Paper, Stack, TextField, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; +import { Controller, useForm } from 'react-hook-form'; +import { LoginFormValues } from '@pages/login/model/types.ts'; +import { LOGIN_FORM_FIELD_NAMES } from '@pages/login/model/constants.ts'; +import { version } from '../../../../package.json'; +import Logo from '@shared/assets/PGCLogo.svg?react'; + +const Login: FC = () => { + const { t } = useTranslation('shared'); + const navigate = useNavigate(); + + const { handleSubmit, control } = useForm(); + + const onSubmit = (values: LoginFormValues) => { + localStorage.setItem('token', values[LOGIN_FORM_FIELD_NAMES.TOKEN]); + navigate(generateAbsoluteRouterPath(RouterPaths.clusters.absolutePath)); + }; + + return ( + + +
+ + + PostgreSQL Cluster Console + ( + + )} + /> + + + v.{version} + + +
+ + + Powered by  + + GS Labs + + + +
+
+ ); +}; + +export default Login; diff --git a/console/ui/src/pages/operation-log/index.ts b/console/ui/src/pages/operation-log/index.ts new file mode 100644 index 000000000..3922eaa48 --- /dev/null +++ b/console/ui/src/pages/operation-log/index.ts @@ -0,0 +1,3 @@ +import OperationLog from '@pages/operation-log/ui'; + +export default OperationLog; diff --git a/console/ui/src/pages/operation-log/ui/index.tsx b/console/ui/src/pages/operation-log/ui/index.tsx new file mode 100644 index 000000000..965f928ee --- /dev/null +++ b/console/ui/src/pages/operation-log/ui/index.tsx @@ -0,0 +1,39 @@ +import { FC, useEffect, useState } from 'react'; +import { Box } from '@mui/material'; +import { useGetOperationsByIdLogQuery } from '@shared/api/api/operations.ts'; +import { useParams } from 'react-router-dom'; +import { LazyLog } from 'react-lazylog'; +import { useQueryPolling } from '@shared/lib/hooks.tsx'; +import { OPERATION_LOGS_POLLING_INTERVAL } from '@shared/config/constants.ts'; + +const OperationLog: FC = () => { + const { operationId } = useParams(); + const [isStopRequest, setIsStopRequest] = useState(false); + + const log = useQueryPolling( + () => useGetOperationsByIdLogQuery({ id: operationId }), + OPERATION_LOGS_POLLING_INTERVAL, + { stop: isStopRequest }, + ); + + useEffect(() => { + setIsStopRequest(!!log.data?.isComplete); + }, [log.data?.isComplete]); + + return ( + + + + ); +}; + +export default OperationLog; diff --git a/console/ui/src/pages/operations/index.ts b/console/ui/src/pages/operations/index.ts new file mode 100644 index 000000000..7f7303f70 --- /dev/null +++ b/console/ui/src/pages/operations/index.ts @@ -0,0 +1,3 @@ +import Operations from '@pages/operations/ui'; + +export default Operations; diff --git a/console/ui/src/pages/operations/ui/index.tsx b/console/ui/src/pages/operations/ui/index.tsx new file mode 100644 index 000000000..20a608281 --- /dev/null +++ b/console/ui/src/pages/operations/ui/index.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import OperationsTable from '@widgets/operations-table'; +import { Box } from '@mui/material'; + +const Operations: FC = () => { + return ( + + + + ); +}; + +export default Operations; diff --git a/console/ui/src/pages/overview-cluster/index.ts b/console/ui/src/pages/overview-cluster/index.ts new file mode 100644 index 000000000..00ec7425c --- /dev/null +++ b/console/ui/src/pages/overview-cluster/index.ts @@ -0,0 +1,3 @@ +import OverviewCluster from '@pages/overview-cluster/ui'; + +export default OverviewCluster; diff --git a/console/ui/src/pages/overview-cluster/ui/index.tsx b/console/ui/src/pages/overview-cluster/ui/index.tsx new file mode 100644 index 000000000..aa49b2669 --- /dev/null +++ b/console/ui/src/pages/overview-cluster/ui/index.tsx @@ -0,0 +1,49 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { useGetClustersByIdQuery } from '@shared/api/api/clusters.ts'; +import { Grid } from '@mui/material'; +import ClusterOverviewTable from '@widgets/cluster-overview-table'; +import ConnectionInfo from '@entities/connection-info'; +import ClusterInfo from '@entities/cluster-info'; +import { useQueryPolling } from '@shared/lib/hooks.tsx'; +import { CLUSTER_OVERVIEW_POLLING_INTERVAL } from '@shared/config/constants.ts'; +import Spinner from '@shared/ui/spinner'; + +const OverviewCluster: FC = () => { + const { t } = useTranslation('clusters'); + const { clusterId } = useParams(); + + const cluster = useQueryPolling(() => useGetClustersByIdQuery({ id: clusterId }), CLUSTER_OVERVIEW_POLLING_INTERVAL); + + const connectionInfo = cluster.data?.connection_info; + + return cluster.isLoading ? ( + + ) : ( + + + + + + + + + + + + ); +}; + +export default OverviewCluster; diff --git a/console/ui/src/pages/settings/index.ts b/console/ui/src/pages/settings/index.ts new file mode 100644 index 000000000..b6b138be8 --- /dev/null +++ b/console/ui/src/pages/settings/index.ts @@ -0,0 +1,3 @@ +import Settings from '@pages/settings/ui'; + +export default Settings; diff --git a/console/ui/src/pages/settings/model/constants.ts b/console/ui/src/pages/settings/model/constants.ts new file mode 100644 index 000000000..57f3e7d6c --- /dev/null +++ b/console/ui/src/pages/settings/model/constants.ts @@ -0,0 +1,20 @@ +import RouterPaths from '@app/router/routerPathsConfig'; + +export const settingsTabsContent = [ + { + translateKey: 'generalSettings', + path: RouterPaths.settings.general.absolutePath, + }, + { + translateKey: 'secrets', + path: RouterPaths.settings.secrets.absolutePath, + }, + { + translateKey: 'projects', + path: RouterPaths.settings.projects.absolutePath, + }, + { + translateKey: 'environments', + path: RouterPaths.settings.environments.absolutePath, + }, +]; diff --git a/console/ui/src/pages/settings/ui/index.tsx b/console/ui/src/pages/settings/ui/index.tsx new file mode 100644 index 000000000..c9b54b0d4 --- /dev/null +++ b/console/ui/src/pages/settings/ui/index.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react'; +import { Divider, Tab, Tabs } from '@mui/material'; +import { Link, Outlet, useLocation } from 'react-router-dom'; +import { settingsTabsContent } from '@pages/settings/model/constants.ts'; +import { useTranslation } from 'react-i18next'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; + +const Settings: FC = () => { + const { t } = useTranslation('settings'); + const location = useLocation(); + + return ( + <> + + {settingsTabsContent.map((tabContent) => ( + + ))} + + + + + ); +}; + +export default Settings; diff --git a/console/ui/src/shared/api/api/clusters.ts b/console/ui/src/shared/api/api/clusters.ts new file mode 100644 index 000000000..548555128 --- /dev/null +++ b/console/ui/src/shared/api/api/clusters.ts @@ -0,0 +1,268 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + postClusters: build.mutation({ + query: (queryArg) => ({ url: `/clusters`, method: 'POST', body: queryArg.requestClusterCreate }), + invalidatesTags: () => [{ type: 'Clusters', id: 'LIST' }], + }), + getClusters: build.query({ + query: (queryArg) => ({ + url: `/clusters`, + params: { + offset: queryArg.offset, + limit: queryArg.limit, + project_id: queryArg.projectId, + name: queryArg.name, + status: queryArg.status, + location: queryArg.location, + environment: queryArg.environment, + server_count: queryArg.serverCount, + postgres_version: queryArg.postgresVersion, + created_at_from: queryArg.createdAtFrom, + created_at_to: queryArg.createdAtTo, + sort_by: queryArg.sortBy, + }, + }), + providesTags: (result) => + result?.data + ? [...result.data.map(({ id }) => ({ type: 'Clusters', id })), { type: 'Clusters', id: 'LIST' }] + : [{ type: 'Clusters', id: 'LIST' }], + }), + getClustersDefaultName: build.query({ + query: () => ({ url: `/clusters/default_name` }), + keepUnusedDataFor: 0, + }), + getClustersById: build.query({ + query: (queryArg) => ({ url: `/clusters/${queryArg.id}` }), + providesTags: (result, error, { id }) => [{ type: 'Clusters', id }], + }), + deleteClustersById: build.mutation({ + query: (queryArg) => ({ url: `/clusters/${queryArg.id}`, method: 'DELETE' }), + invalidatesTags: () => [{ type: 'Clusters', id: 'LIST' }], + }), + postClustersByIdRefresh: build.mutation({ + query: (queryArg) => ({ url: `/clusters/${queryArg.id}/refresh`, method: 'POST' }), + invalidatesTags: (result, error, { id }) => [{ type: 'Clusters', id }], + }), + postClustersByIdReinit: build.mutation({ + query: (queryArg) => ({ + url: `/clusters/${queryArg.id}/reinit`, + method: 'POST', + body: queryArg.requestClusterReinit, + }), + invalidatesTags: (result, error, { id }) => [{ type: 'Clusters', id }], + }), + postClustersByIdReload: build.mutation({ + query: (queryArg) => ({ + url: `/clusters/${queryArg.id}/reload`, + method: 'POST', + body: queryArg.requestClusterReload, + }), + invalidatesTags: (result, error, { id }) => [{ type: 'Clusters', id }], + }), + postClustersByIdRestart: build.mutation({ + query: (queryArg) => ({ + url: `/clusters/${queryArg.id}/restart`, + method: 'POST', + body: queryArg.requestClusterRestart, + }), + invalidatesTags: (result, error, { id }) => [{ type: 'Clusters', id }], + }), + postClustersByIdStop: build.mutation({ + query: (queryArg) => ({ + url: `/clusters/${queryArg.id}/stop`, + method: 'POST', + body: queryArg.requestClusterStop, + }), + invalidatesTags: (result, error, { id }) => [{ type: 'Clusters', id }], + }), + postClustersByIdStart: build.mutation({ + query: (queryArg) => ({ + url: `/clusters/${queryArg.id}/start`, + method: 'POST', + body: queryArg.requestClusterStart, + }), + invalidatesTags: (result, error, { id }) => [{ type: 'Clusters', id }], + }), + postClustersByIdRemove: build.mutation({ + query: (queryArg) => ({ + url: `/clusters/${queryArg.id}/remove`, + method: 'POST', + body: queryArg.requestClusterRemove, + }), + invalidatesTags: () => [{ type: 'Clusters', id: 'LIST' }], + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as clustersApi }; +export type PostClustersApiResponse = /** status 200 OK */ ResponseClusterCreate; +export type PostClustersApiArg = { + requestClusterCreate: RequestClusterCreate; +}; +export type GetClustersApiResponse = /** status 200 OK */ ResponseClustersInfo; +export type GetClustersApiArg = { + offset?: number; + limit?: number; + projectId: number; + /** Filter by name */ + name?: string; + /** Filter by status */ + status?: string; + /** Filter by location */ + location?: string; + /** Filter by environment */ + environment?: string; + /** Filter by server_count */ + serverCount?: number; + /** Filter by postgres_version */ + postgresVersion?: number; + /** Created at after this date */ + createdAtFrom?: string; + /** Created at till this date */ + createdAtTo?: string; + /** Sort by fields. Example: sort_by=id,-name,created_at,updated_at + Supported values: + - id + - name + - created_at + - updated_at + - environment + - project + - status + - location + - server_count + - postgres_version + */ + sortBy?: string; +}; +export type GetClustersDefaultNameApiResponse = /** status 200 OK */ ResponseClusterDefaultName; +export type GetClustersDefaultNameApiArg = void; +export type GetClustersByIdApiResponse = /** status 200 OK */ ClusterInfo; +export type GetClustersByIdApiArg = { + id: number; +}; +export type DeleteClustersByIdApiResponse = /** status 204 OK */ void; +export type DeleteClustersByIdApiArg = { + id: number; +}; +export type PostClustersByIdRefreshApiResponse = /** status 200 OK */ ClusterInfo; +export type PostClustersByIdRefreshApiArg = { + id: number; +}; +export type PostClustersByIdReinitApiResponse = /** status 200 OK */ ResponseClusterCreate; +export type PostClustersByIdReinitApiArg = { + id: number; + requestClusterReinit: RequestClusterReinit; +}; +export type PostClustersByIdReloadApiResponse = /** status 200 OK */ ResponseClusterCreate; +export type PostClustersByIdReloadApiArg = { + id: number; + requestClusterReload: RequestClusterReload; +}; +export type PostClustersByIdRestartApiResponse = /** status 200 OK */ ResponseClusterCreate; +export type PostClustersByIdRestartApiArg = { + id: number; + requestClusterRestart: RequestClusterRestart; +}; +export type PostClustersByIdStopApiResponse = /** status 200 OK */ ResponseClusterCreate; +export type PostClustersByIdStopApiArg = { + id: number; + requestClusterStop: RequestClusterStop; +}; +export type PostClustersByIdStartApiResponse = /** status 200 OK */ ResponseClusterCreate; +export type PostClustersByIdStartApiArg = { + id: number; + requestClusterStart: RequestClusterStart; +}; +export type PostClustersByIdRemoveApiResponse = /** status 204 OK */ void; +export type PostClustersByIdRemoveApiArg = { + id: number; + requestClusterRemove: RequestClusterRemove; +}; +export type ResponseClusterCreate = { + /** unique code for cluster */ + cluster_id?: number; +}; +export type ErrorObject = { + code?: number; + title?: string; + description?: string; +}; +export type RequestClusterCreate = { + name?: string; + /** Info about cluster */ + description?: string; + /** Info for deployment system authorization */ + auth_info?: { + secret_id?: number; + }; + /** Project for new cluster */ + project_id?: number; + /** Project environment */ + environment_id?: number; + envs?: string[]; + extra_vars?: string[]; +}; +export type ClusterInfoInstance = { + id?: number; + name?: string; + ip?: string; + status?: string; + role?: string; + timeline?: number | null; + lag?: number | null; + tags?: object; + pending_restart?: boolean | null; +}; +export type ClusterInfo = { + id?: number; + name?: string; + description?: string; + status?: string; + creation_time?: string; + environment?: string; + servers?: ClusterInfoInstance[]; + postgres_version?: number; + /** Code of location */ + cluster_location?: string; + /** Project for cluster */ + project_name?: string; + connection_info?: object; +}; +export type PaginationInfoForListRequests = { + offset?: number | null; + limit?: number | null; + count?: number | null; +}; +export type ResponseClustersInfo = { + data?: ClusterInfo[]; + meta?: PaginationInfoForListRequests; +}; +export type ResponseClusterDefaultName = { + name?: string; +}; +export type RequestClusterReinit = object; +export type RequestClusterReload = object; +export type RequestClusterRestart = object; +export type RequestClusterStop = object; +export type RequestClusterStart = object; +export type RequestClusterRemove = object; +export const { + usePostClustersMutation, + useGetClustersQuery, + useLazyGetClustersQuery, + useGetClustersDefaultNameQuery, + useLazyGetClustersDefaultNameQuery, + useGetClustersByIdQuery, + useLazyGetClustersByIdQuery, + useDeleteClustersByIdMutation, + usePostClustersByIdRefreshMutation, + usePostClustersByIdReinitMutation, + usePostClustersByIdReloadMutation, + usePostClustersByIdRestartMutation, + usePostClustersByIdStopMutation, + usePostClustersByIdStartMutation, + usePostClustersByIdRemoveMutation, +} = injectedRtkApi; diff --git a/console/ui/src/shared/api/api/deployments.ts b/console/ui/src/shared/api/api/deployments.ts new file mode 100644 index 000000000..87fdca634 --- /dev/null +++ b/console/ui/src/shared/api/api/deployments.ts @@ -0,0 +1,94 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + getExternalDeployments: build.query({ + query: (queryArg) => ({ + url: `/external/deployments`, + params: { offset: queryArg.offset, limit: queryArg.limit }, + }), + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as deploymentsApi }; +export type GetExternalDeploymentsApiResponse = /** status 200 OK */ DeploymentsInfo; +export type GetExternalDeploymentsApiArg = { + offset?: number; + limit?: number; +}; +export type DeploymentCloudImage = { + image?: object; + arch?: string; + os_name?: string; + os_version?: string; + updated_at?: string; +}; +export type DeploymentInfoCloudRegion = { + /** unique parameter for DB */ + code?: string; + /** Field for web */ + name?: string; + /** List of datacenters for this region */ + datacenters?: { + code?: string; + location?: string; + cloud_image?: DeploymentCloudImage; + }[]; +}; +export type DeploymentInstanceType = { + code?: string; + cpu?: number; + ram?: number; + /** Price for 1 instance by hour */ + price_hourly?: number; + /** Price for 1 instance by month */ + price_monthly?: number; + /** Price currency */ + currency?: string; +}; +export type ResponseDeploymentInfo = { + code?: string; + description?: string; + avatar_url?: string; + /** List of available regions for current deployment */ + cloud_regions?: DeploymentInfoCloudRegion[]; + /** Lists of available instance types */ + instance_types?: { + small?: DeploymentInstanceType[] | null; + medium?: DeploymentInstanceType[]; + large?: DeploymentInstanceType[]; + }; + /** Hardware disks info */ + volumes?: { + /** Volume type */ + volume_type?: string; + /** Volume description */ + volume_description?: string; + /** Sets in GB */ + min_size?: number; + /** Sets in GB */ + max_size?: number; + /** Price for disk by months */ + price_monthly?: number; + /** Price currency */ + currency?: string; + /** Default volume */ + is_default?: boolean | null; + }[]; +}; +export type PaginationInfoForListRequests = { + offset?: number | null; + limit?: number | null; + count?: number | null; +}; +export type DeploymentsInfo = { + data?: ResponseDeploymentInfo[]; + meta?: PaginationInfoForListRequests; +}; +export type ErrorObject = { + code?: number; + title?: string; + description?: string; +}; +export const { useGetExternalDeploymentsQuery, useLazyGetExternalDeploymentsQuery } = injectedRtkApi; diff --git a/console/ui/src/shared/api/api/environments.ts b/console/ui/src/shared/api/api/environments.ts new file mode 100644 index 000000000..5fedef2e5 --- /dev/null +++ b/console/ui/src/shared/api/api/environments.ts @@ -0,0 +1,70 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + getEnvironments: build.query({ + query: (queryArg) => ({ url: `/environments`, params: { limit: queryArg.limit, offset: queryArg.offset } }), + providesTags: (result) => + result?.data + ? [ + ...result.data.map(({ id }) => ({ type: 'Environments', id }) as const), + { type: 'Environments', id: 'LIST' }, + ] + : [{ type: 'Environments', id: 'LIST' }], + }), + postEnvironments: build.mutation({ + query: (queryArg) => ({ url: `/environments`, method: 'POST', body: queryArg.requestEnvironment }), + invalidatesTags: () => [{ type: 'Environments', id: 'LIST' }], + }), + deleteEnvironmentsById: build.mutation({ + query: (queryArg) => ({ url: `/environments/${queryArg.id}`, method: 'DELETE' }), + invalidatesTags: () => [{ type: 'Environments', id: 'LIST' }], + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as environmentsApi }; +export type GetEnvironmentsApiResponse = /** status 200 OK */ ResponseEnvironmentsList; +export type GetEnvironmentsApiArg = { + limit?: number; + offset?: number; +}; +export type PostEnvironmentsApiResponse = /** status 200 OK */ ResponseEnvironment; +export type PostEnvironmentsApiArg = { + requestEnvironment: RequestEnvironment; +}; +export type DeleteEnvironmentsByIdApiResponse = /** status 204 OK */ void; +export type DeleteEnvironmentsByIdApiArg = { + id: number; +}; +export type ResponseEnvironment = { + id?: number; + name?: string; + description?: string | null; + created_at?: string; + updated_at?: string | null; +}; +export type PaginationInfoForListRequests = { + offset?: number | null; + limit?: number | null; + count?: number | null; +}; +export type ResponseEnvironmentsList = { + data?: ResponseEnvironment[]; + meta?: PaginationInfoForListRequests; +}; +export type ErrorObject = { + code?: number; + title?: string; + description?: string; +}; +export type RequestEnvironment = { + name?: string; + description?: string; +}; +export const { + useGetEnvironmentsQuery, + useLazyGetEnvironmentsQuery, + usePostEnvironmentsMutation, + useDeleteEnvironmentsByIdMutation, +} = injectedRtkApi; diff --git a/console/ui/src/shared/api/api/operations.ts b/console/ui/src/shared/api/api/operations.ts new file mode 100644 index 000000000..b8517ebb1 --- /dev/null +++ b/console/ui/src/shared/api/api/operations.ts @@ -0,0 +1,89 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + getOperations: build.query({ + query: (queryArg) => ({ + url: `/operations`, + params: { + project_id: queryArg.projectId, + start_date: queryArg.startDate, + end_date: queryArg.endDate, + cluster_name: queryArg.clusterName, + type: queryArg['type'], + status: queryArg.status, + sort_by: queryArg.sortBy, + limit: queryArg.limit, + offset: queryArg.offset, + }, + }), + providesTags: (result) => + result?.data + ? [...result.data.map(({ id }) => ({ type: 'Operations', id }) as const), { type: 'Operations', id: 'LIST' }] + : [{ type: 'Operations', id: 'LIST' }], + }), + getOperationsByIdLog: build.query({ + query: (queryArg) => ({ url: `/operations/${queryArg.id}/log` }), + transformResponse: (response, meta) => ({ + log: response, + isComplete: meta.response.headers.get('x-log-completed')?.toString() === 'true', + }), + providesTags: (result, error, { id }) => [{ type: 'Operations', id }], + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as operationsApi }; +export type GetOperationsApiResponse = /** status 200 OK */ ResponseOperationsList; +export type GetOperationsApiArg = { + /** Required parameter for filter */ + projectId: number; + /** Operations started after this date */ + startDate: string; + /** Operations started till this date */ + endDate: string; + /** Filter by cluster_name */ + clusterName?: string; + /** Filter by type */ + type?: string; + /** Filter by status */ + status?: string; + /** Sort by fields. Example: sort_by=cluster_name,-type,status,id */ + sortBy?: string; + limit?: number; + offset?: number; +}; +export type GetOperationsByIdLogApiResponse = /** status 200 OK */ string; +export type GetOperationsByIdLogApiArg = { + /** Operation id */ + id: number; +}; +export type ResponseOperation = { + id?: number; + cluster_name?: string; + started?: string; + finished?: string | null; + type?: string; + status?: string; + environment?: string; +}; +export type PaginationInfoForListRequests = { + offset?: number | null; + limit?: number | null; + count?: number | null; +}; +export type ResponseOperationsList = { + data?: ResponseOperation[]; + meta?: PaginationInfoForListRequests; +}; +export type ErrorObject = { + code?: number; + title?: string; + description?: string; +}; +export const { + useGetOperationsQuery, + useLazyGetOperationsQuery, + useGetOperationsByIdLogQuery, + useLazyGetOperationsByIdLogQuery, +} = injectedRtkApi; diff --git a/console/ui/src/shared/api/api/other.ts b/console/ui/src/shared/api/api/other.ts new file mode 100644 index 000000000..dcc529c88 --- /dev/null +++ b/console/ui/src/shared/api/api/other.ts @@ -0,0 +1,86 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + getVersion: build.query({ + query: () => ({ url: `/version` }), + }), + getDatabaseExtensions: build.query({ + query: (queryArg) => ({ + url: `/database/extensions`, + params: { + offset: queryArg.offset, + limit: queryArg.limit, + extension_type: queryArg.extensionType, + postgres_version: queryArg.postgresVersion, + }, + }), + }), + getPostgresVersions: build.query({ + query: () => ({ url: `/postgres_versions` }), + }), + deleteServersById: build.mutation({ + query: (queryArg) => ({ url: `/servers/${queryArg.id}`, method: 'DELETE' }), + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as otherApi }; +export type GetVersionApiResponse = /** status 200 OK */ VersionResponse; +export type GetVersionApiArg = void; +export type GetDatabaseExtensionsApiResponse = /** status 200 OK */ ResponseDatabaseExtensions; +export type GetDatabaseExtensionsApiArg = { + offset?: number; + limit?: number; + extensionType?: 'all' | 'contrib' | 'third_party'; + postgresVersion?: string; +}; +export type GetPostgresVersionsApiResponse = /** status 200 OK */ ResponsePostgresVersions; +export type GetPostgresVersionsApiArg = void; +export type DeleteServersByIdApiResponse = /** status 204 OK */ void; +export type DeleteServersByIdApiArg = { + id: number; +}; +export type VersionResponse = { + version?: string; +}; +export type ResponseDatabaseExtension = { + name?: string; + description?: string | null; + url?: string | null; + image?: string | null; + postgres_min_version?: string | null; + postgres_max_version?: string | null; + contrib?: boolean; +}; +export type PaginationInfoForListRequests = { + offset?: number | null; + limit?: number | null; + count?: number | null; +}; +export type ResponseDatabaseExtensions = { + data?: ResponseDatabaseExtension[]; + meta?: PaginationInfoForListRequests; +}; +export type ErrorObject = { + code?: number; + title?: string; + description?: string; +}; +export type ResponsePostgresVersion = { + major_version?: number; + release_date?: string; + end_of_life?: string; +}; +export type ResponsePostgresVersions = { + data?: ResponsePostgresVersion[]; +}; +export const { + useGetVersionQuery, + useLazyGetVersionQuery, + useGetDatabaseExtensionsQuery, + useLazyGetDatabaseExtensionsQuery, + useGetPostgresVersionsQuery, + useLazyGetPostgresVersionsQuery, + useDeleteServersByIdMutation, +} = injectedRtkApi; diff --git a/console/ui/src/shared/api/api/projects.ts b/console/ui/src/shared/api/api/projects.ts new file mode 100644 index 000000000..08642f759 --- /dev/null +++ b/console/ui/src/shared/api/api/projects.ts @@ -0,0 +1,81 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + postProjects: build.mutation({ + query: (queryArg) => ({ url: `/projects`, method: 'POST', body: queryArg.requestProjectCreate }), + invalidatesTags: () => [{ type: 'Projects', id: 'LIST' }], + }), + getProjects: build.query({ + query: (queryArg) => ({ url: `/projects`, params: { limit: queryArg.limit, offset: queryArg.offset } }), + providesTags: (result) => + result?.data + ? [...result.data.map(({ id }) => ({ type: 'Projects', id }) as const), { type: 'Projects', id: 'LIST' }] + : [{ type: 'Projects', id: 'LIST' }], + }), + patchProjectsById: build.mutation({ + query: (queryArg) => ({ url: `/projects/${queryArg.id}`, method: 'PATCH', body: queryArg.requestProjectPatch }), + invalidatesTags: (result, error, { id }) => [{ type: 'Projects', id }], + }), + deleteProjectsById: build.mutation({ + query: (queryArg) => ({ url: `/projects/${queryArg.id}`, method: 'DELETE' }), + invalidatesTags: () => [{ type: 'Projects', id: 'LIST' }], + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as projectsApi }; +export type PostProjectsApiResponse = /** status 200 OK */ ResponseProject; +export type PostProjectsApiArg = { + requestProjectCreate: RequestProjectCreate; +}; +export type GetProjectsApiResponse = /** status 200 OK */ ResponseProjectsList; +export type GetProjectsApiArg = { + limit?: number; + offset?: number; +}; +export type PatchProjectsByIdApiResponse = /** status 200 OK */ ResponseProject; +export type PatchProjectsByIdApiArg = { + id: number; + requestProjectPatch: RequestProjectPatch; +}; +export type DeleteProjectsByIdApiResponse = /** status 204 OK */ void; +export type DeleteProjectsByIdApiArg = { + id: number; +}; +export type ResponseProject = { + id?: number; + name?: string; + description?: string | null; + created_at?: string; + updated_at?: string | null; +}; +export type ErrorObject = { + code?: number; + title?: string; + description?: string; +}; +export type RequestProjectCreate = { + name?: string; + description?: string; +}; +export type PaginationInfoForListRequests = { + offset?: number | null; + limit?: number | null; + count?: number | null; +}; +export type ResponseProjectsList = { + data?: ResponseProject[]; + meta?: PaginationInfoForListRequests; +}; +export type RequestProjectPatch = { + name?: string | null; + description?: string | null; +}; +export const { + usePostProjectsMutation, + useGetProjectsQuery, + useLazyGetProjectsQuery, + usePatchProjectsByIdMutation, + useDeleteProjectsByIdMutation, +} = injectedRtkApi; diff --git a/console/ui/src/shared/api/api/secrets.ts b/console/ui/src/shared/api/api/secrets.ts new file mode 100644 index 000000000..b9b385499 --- /dev/null +++ b/console/ui/src/shared/api/api/secrets.ts @@ -0,0 +1,164 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + postSecrets: build.mutation({ + query: (queryArg) => ({ url: `/secrets`, method: 'POST', body: queryArg.requestSecretCreate }), + invalidatesTags: () => [{ type: 'Secrets', id: 'LIST' }], + }), + getSecrets: build.query({ + query: (queryArg) => ({ + url: `/secrets`, + params: { + limit: queryArg.limit, + offset: queryArg.offset, + project_id: queryArg.projectId, + name: queryArg.name, + type: queryArg.type, + sort_by: queryArg.sortBy, + }, + }), + providesTags: (result) => + result?.data + ? [...result.data.map(({ id }) => ({ type: 'Secrets', id }) as const), { type: 'Secrets', id: 'LIST' }] + : [{ type: 'Secrets', id: 'LIST' }], + }), + patchSecretsById: build.mutation({ + query: (queryArg) => ({ url: `/secrets/${queryArg.id}`, method: 'PATCH', body: queryArg.requestSecretPatch }), + invalidatesTags: (result, error, { id }) => [{ type: 'Secrets', id }], + }), + deleteSecretsById: build.mutation({ + query: (queryArg) => ({ url: `/secrets/${queryArg.id}`, method: 'DELETE' }), + invalidatesTags: () => [{ type: 'Secrets', id: 'LIST' }], + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as secretsApi }; +export type PostSecretsApiResponse = /** status 200 OK */ ResponseSecretInfo; + +export interface PostSecretsApiArg { + requestSecretCreate: RequestSecretCreate; +} + +export type GetSecretsApiResponse = /** status 200 OK */ ResponseSecretInfoList; + +export interface GetSecretsApiArg { + limit?: number; + offset?: number; + projectId: number; + /** Filter by name */ + name?: string; + /** Filter by type */ + type?: string; + /** Sort by fields. Example: sort_by=id,name,-type */ + sortBy?: string; +} + +export type PatchSecretsByIdApiResponse = /** status 200 OK */ ResponseSecretInfo; + +export interface PatchSecretsByIdApiArg { + id: number; + requestSecretPatch: RequestSecretPatch; +} + +export type DeleteSecretsByIdApiResponse = /** status 204 OK */ void; + +export interface DeleteSecretsByIdApiArg { + id: number; +} + +export type SecretType = 'aws' | 'gcp' | 'hetzner' | 'ssh_key' | 'digitalocean' | 'password' | 'azure'; + +export interface ResponseSecretInfo { + id?: number; + project_id?: number; + name?: string; + type?: SecretType; + created_at?: string; + updated_at?: string | null; + is_used?: boolean; + used_by_clusters?: string | null; +} + +export interface ErrorObject { + code?: number; + title?: string; + description?: string; +} + +export interface RequestSecretValueAws { + AWS_ACCESS_KEY_ID?: string; + AWS_SECRET_ACCESS_KEY?: string; +} + +export interface RequestSecretValueGcp { + GCP_SERVICE_ACCOUNT_CONTENTS?: string; +} + +export interface RequestSecretValueHetzner { + HCLOUD_API_TOKEN?: string; +} + +export interface RequestSecretValueSshKey { + SSH_PRIVATE_KEY?: string; +} + +export interface RequestSecretValueDigitalOcean { + DO_API_TOKEN?: string; +} + +export interface RequestSecretValuePassword { + USERNAME?: string; + PASSWORD?: string; +} + +export interface RequestSecretValueAzure { + AZURE_SUBSCRIPTION_ID?: string; + AZURE_CLIENT_ID?: string; + AZURE_SECRET?: string; + AZURE_TENANT?: string; +} + +export interface RequestSecretValue { + aws?: RequestSecretValueAws; + gcp?: RequestSecretValueGcp; + hetzner?: RequestSecretValueHetzner; + ssh_key?: RequestSecretValueSshKey; + digitalocean?: RequestSecretValueDigitalOcean; + password?: RequestSecretValuePassword; + azure?: RequestSecretValueAzure; +} + +export interface RequestSecretCreate { + project_id?: number; + name?: string; + type?: SecretType; + value?: RequestSecretValue; +} + +export interface PaginationInfoForListRequests { + offset?: number | null; + limit?: number | null; + count?: number | null; +} + +export interface ResponseSecretInfoList { + data?: ResponseSecretInfo[]; + meta?: PaginationInfoForListRequests; +} + +export interface RequestSecretPatch { + name?: string | null; + type?: string | null; + /** Secret value in base64 */ + value?: string | null; +} + +export const { + usePostSecretsMutation, + useGetSecretsQuery, + useLazyGetSecretsQuery, + usePatchSecretsByIdMutation, + useDeleteSecretsByIdMutation, +} = injectedRtkApi; diff --git a/console/ui/src/shared/api/api/settings.ts b/console/ui/src/shared/api/api/settings.ts new file mode 100644 index 000000000..e40d90f83 --- /dev/null +++ b/console/ui/src/shared/api/api/settings.ts @@ -0,0 +1,76 @@ +import { baseApi as api } from '../baseApi.ts'; + +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + postSettings: build.mutation({ + query: (queryArg) => ({ url: `/settings`, method: 'POST', body: queryArg.requestCreateSetting }), + invalidatesTags: () => [{ type: 'Settings', id: 'LIST' }], + }), + getSettings: build.query({ + query: (queryArg) => ({ + url: `/settings`, + params: { name: queryArg.name, offset: queryArg.offset, limit: queryArg.limit }, + }), + providesTags: (result) => + result?.data + ? [...result.data.map(({ id }) => ({ type: 'Settings', id }) as const), { type: 'Settings', id: 'LIST' }] + : [{ type: 'Settings', id: 'LIST' }], + }), + patchSettingsByName: build.mutation({ + query: (queryArg) => ({ + url: `/settings/${queryArg.name}`, + method: 'PATCH', + body: queryArg.requestChangeSetting, + }), + invalidatesTags: (result, error, { id }) => [{ type: 'Settings', id }], + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as settingsApi }; +export type PostSettingsApiResponse = /** status 200 OK */ ResponseSetting; +export type PostSettingsApiArg = { + requestCreateSetting: RequestCreateSetting; +}; +export type GetSettingsApiResponse = /** status 200 OK */ ResponseSettings; +export type GetSettingsApiArg = { + /** Filter by name */ + name?: string; + offset?: number; + limit?: number; +}; +export type PatchSettingsByNameApiResponse = /** status 200 OK */ ResponseSetting; +export type PatchSettingsByNameApiArg = { + name: string; + requestChangeSetting: RequestChangeSetting; +}; +export type ResponseSetting = { + id?: number; + name?: string; + value?: object; + created_at?: string; + updated_at?: string | null; +}; +export type ErrorObject = { + code?: number; + title?: string; + description?: string; +}; +export type RequestCreateSetting = { + name?: string; + value?: object; +}; +export type PaginationInfoForListRequests = { + offset?: number | null; + limit?: number | null; + count?: number | null; +}; +export type ResponseSettings = { + data?: ResponseSetting[]; + mete?: PaginationInfoForListRequests; +}; +export type RequestChangeSetting = { + value?: object | null; +}; +export const { usePostSettingsMutation, useGetSettingsQuery, useLazyGetSettingsQuery, usePatchSettingsByNameMutation } = + injectedRtkApi; diff --git a/console/ui/src/shared/api/apiConfig.ts b/console/ui/src/shared/api/apiConfig.ts new file mode 100644 index 000000000..bb3d13457 --- /dev/null +++ b/console/ui/src/shared/api/apiConfig.ts @@ -0,0 +1,45 @@ +import type { ConfigFile } from '@rtk-query/codegen-openapi'; + +const config: ConfigFile = { + schemaFile: '../../../../service/api/swagger.yaml', + apiFile: './baseApi.ts', + apiImport: 'baseApi', + outputFiles: { + './generatedApi/clusters.ts': { + filterEndpoints: [/cluster/i], + exportName: 'clustersApi', + }, + './generatedApi/environments.ts': { + filterEndpoints: [/environment/i], + exportName: 'environmentsApi', + }, + './generatedApi/projects.ts': { + filterEndpoints: [/project/i], + exportName: 'projectsApi', + }, + './generatedApi/secrets.ts': { + filterEndpoints: [/secret/i], + exportName: 'secretsApi', + }, + './generatedApi/operations.ts': { + filterEndpoints: [/operation/i], + exportName: 'operationsApi', + }, + './generatedApi/deployments.ts': { + filterEndpoints: [/deployment/i], + exportName: 'deploymentsApi', + }, + './generatedApi/settings.ts': { + filterEndpoints: [/settings/i], + exportName: 'settingsApi', + }, + './generatedApi/other.ts': { + filterEndpoints: [/^((?!(cluster|environment|project|secret|operation|deployment|settings)).)*$/i], + exportName: 'otherApi', + }, + }, + exportName: 'postgresClusterConsoleApi', + hooks: { queries: true, lazyQueries: true, mutations: true }, +}; + +export default config; diff --git a/console/ui/src/shared/api/baseApi.ts b/console/ui/src/shared/api/baseApi.ts new file mode 100644 index 000000000..efdc6d703 --- /dev/null +++ b/console/ui/src/shared/api/baseApi.ts @@ -0,0 +1,16 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { API_URL } from '@shared/config/constants.ts'; +import i18n from 'i18next'; + +export const baseApi = createApi({ + baseQuery: fetchBaseQuery({ + baseUrl: API_URL as string, + prepareHeaders: (headers, { endpoint }) => { + headers.set('Accept-Language', i18n.language); + if (endpoint !== 'login') headers.set('Authorization', `Bearer ${String(localStorage.getItem('token'))}`); + return headers; + }, + }), + tagTypes: ['Clusters', 'Operations', 'Secrets', 'Projects', 'Environments', 'Settings'], + endpoints: () => ({}), +}); diff --git a/console/ui/src/shared/api/enhancedSecretsApi.ts b/console/ui/src/shared/api/enhancedSecretsApi.ts new file mode 100644 index 000000000..a51cbdaa2 --- /dev/null +++ b/console/ui/src/shared/api/enhancedSecretsApi.ts @@ -0,0 +1,15 @@ +import { secretsApi } from '@shared/api/api/secrets.ts'; + +const enhancedSecretsApi = secretsApi.enhanceEndpoints({ + addTagTypes: ['Secrets'], + endpoints: { + getSecrets: { + providesTags: ['Secrets'], + }, + postSecrets: { + invalidatesTags: ['Secrets'], + }, + }, +}); + +export default enhancedSecretsApi; diff --git a/console/ui/src/shared/assets/PGCLogo.svg b/console/ui/src/shared/assets/PGCLogo.svg new file mode 100644 index 000000000..a056ae882 --- /dev/null +++ b/console/ui/src/shared/assets/PGCLogo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/console/ui/src/shared/assets/calendarClockICon.svg b/console/ui/src/shared/assets/calendarClockICon.svg new file mode 100644 index 000000000..d7f110ca2 --- /dev/null +++ b/console/ui/src/shared/assets/calendarClockICon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/checkIcon.svg b/console/ui/src/shared/assets/checkIcon.svg new file mode 100644 index 000000000..cb4912567 --- /dev/null +++ b/console/ui/src/shared/assets/checkIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/clustersIcon.svg b/console/ui/src/shared/assets/clustersIcon.svg new file mode 100644 index 000000000..07dadf4fb --- /dev/null +++ b/console/ui/src/shared/assets/clustersIcon.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/shared/assets/collapseIcon.svg b/console/ui/src/shared/assets/collapseIcon.svg new file mode 100644 index 000000000..028af5dec --- /dev/null +++ b/console/ui/src/shared/assets/collapseIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/cpuIcon.svg b/console/ui/src/shared/assets/cpuIcon.svg new file mode 100644 index 000000000..a1584a84f --- /dev/null +++ b/console/ui/src/shared/assets/cpuIcon.svg @@ -0,0 +1,2 @@ + + diff --git a/console/ui/src/shared/assets/databaseIcon.svg b/console/ui/src/shared/assets/databaseIcon.svg new file mode 100644 index 000000000..4050d1013 --- /dev/null +++ b/console/ui/src/shared/assets/databaseIcon.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/shared/assets/docsIcon.svg b/console/ui/src/shared/assets/docsIcon.svg new file mode 100644 index 000000000..30baf2753 --- /dev/null +++ b/console/ui/src/shared/assets/docsIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/flagIcon.svg b/console/ui/src/shared/assets/flagIcon.svg new file mode 100644 index 000000000..f0bd3555f --- /dev/null +++ b/console/ui/src/shared/assets/flagIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/console/ui/src/shared/assets/githubIcon.svg b/console/ui/src/shared/assets/githubIcon.svg new file mode 100644 index 000000000..d9a232c77 --- /dev/null +++ b/console/ui/src/shared/assets/githubIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/instanceIcon.svg b/console/ui/src/shared/assets/instanceIcon.svg new file mode 100644 index 000000000..cc86f34a8 --- /dev/null +++ b/console/ui/src/shared/assets/instanceIcon.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/console/ui/src/shared/assets/lanIcon.svg b/console/ui/src/shared/assets/lanIcon.svg new file mode 100644 index 000000000..d720f1904 --- /dev/null +++ b/console/ui/src/shared/assets/lanIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/logoutIcon.svg b/console/ui/src/shared/assets/logoutIcon.svg new file mode 100644 index 000000000..ecd6beb3d --- /dev/null +++ b/console/ui/src/shared/assets/logoutIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/memoryIcon.svg b/console/ui/src/shared/assets/memoryIcon.svg new file mode 100644 index 000000000..c17bf6a46 --- /dev/null +++ b/console/ui/src/shared/assets/memoryIcon.svg @@ -0,0 +1,2 @@ + + diff --git a/console/ui/src/shared/assets/operationsIcon.svg b/console/ui/src/shared/assets/operationsIcon.svg new file mode 100644 index 000000000..106e3b29a --- /dev/null +++ b/console/ui/src/shared/assets/operationsIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/ramIcon.svg b/console/ui/src/shared/assets/ramIcon.svg new file mode 100644 index 000000000..ce5a9e135 --- /dev/null +++ b/console/ui/src/shared/assets/ramIcon.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/shared/assets/serversIcon.svg b/console/ui/src/shared/assets/serversIcon.svg new file mode 100644 index 000000000..e0f3b08e6 --- /dev/null +++ b/console/ui/src/shared/assets/serversIcon.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/shared/assets/settingsIcon.svg b/console/ui/src/shared/assets/settingsIcon.svg new file mode 100644 index 000000000..59ba4ea07 --- /dev/null +++ b/console/ui/src/shared/assets/settingsIcon.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/shared/assets/sponsorIcon.svg b/console/ui/src/shared/assets/sponsorIcon.svg new file mode 100644 index 000000000..53e1ee1a5 --- /dev/null +++ b/console/ui/src/shared/assets/sponsorIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/assets/storageIcon.svg b/console/ui/src/shared/assets/storageIcon.svg new file mode 100644 index 000000000..ba2cfa33d --- /dev/null +++ b/console/ui/src/shared/assets/storageIcon.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/console/ui/src/shared/assets/supportIcon.svg b/console/ui/src/shared/assets/supportIcon.svg new file mode 100644 index 000000000..805593207 --- /dev/null +++ b/console/ui/src/shared/assets/supportIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/shared/config/constants.ts b/console/ui/src/shared/config/constants.ts new file mode 100644 index 000000000..a58153d65 --- /dev/null +++ b/console/ui/src/shared/config/constants.ts @@ -0,0 +1,32 @@ +import { getEnvVariable } from '@shared/lib/functions.ts'; + +export const LOCALES = Object.freeze({ + EN_US: 'en', +}); + +export const API_URL = getEnvVariable('VITE_API_URL'); +export const AUTH_TOKEN = getEnvVariable('VITE_AUTH_TOKEN'); +export const CLUSTERS_POLLING_INTERVAL = getEnvVariable('VITE_CLUSTERS_POLLING_INTERVAL'); +export const CLUSTER_OVERVIEW_POLLING_INTERVAL = getEnvVariable('VITE_CLUSTER_OVERVIEW_POLLING_INTERVAL'); +export const OPERATIONS_POLLING_INTERVAL = getEnvVariable('VITE_OPERATIONS_POLLING_INTERVAL'); +export const OPERATION_LOGS_POLLING_INTERVAL = getEnvVariable('VITE_OPERATION_LOGS_POLLING_INTERVAL'); + +export const PAGINATION_LIMIT_OPTIONS = Object.freeze([ + { value: 5, label: 5 }, + { value: 10, label: 10 }, + { + value: 25, + label: 25, + }, + { value: 50, label: 50 }, + { value: 100, label: 100 }, +]); + +export const PROVIDERS = Object.freeze({ + AWS: 'aws', + GCP: 'gcp', + AZURE: 'azure', + DIGITAL_OCEAN: 'digitalocean', + HETZNER: 'hetzner', + LOCAL: 'local', +}); diff --git a/console/ui/src/shared/i18n/i18n.ts b/console/ui/src/shared/i18n/i18n.ts new file mode 100644 index 000000000..1e931c145 --- /dev/null +++ b/console/ui/src/shared/i18n/i18n.ts @@ -0,0 +1,36 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import shared from './locales/en/shared.json'; +import operations from './locales/en/operations.json'; +import settings from './locales/en/settings.json'; +import clusters from './locales/en/clusters.json'; +import validation from './locales/en/validation.json'; +import toasts from './locales/en/toasts.json'; +import { LOCALES } from '../config/constants'; + +import LanguageDetector from 'i18next-browser-languagedetector'; + +const resources = { + en: { + shared, + clusters, + operations, + settings, + validation, + toasts, + }, +}; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources, + ns: ['shared', 'clusters', 'operations', 'settings', 'validation', 'toasts'], + fallbackLng: LOCALES.EN_US, + supportedLngs: Object.values(LOCALES), + returnNull: false, + debug: false, + }); + +export default i18n; diff --git a/console/ui/src/shared/i18n/locales/en/clusters.json b/console/ui/src/shared/i18n/locales/en/clusters.json new file mode 100644 index 000000000..cc0a45824 --- /dev/null +++ b/console/ui/src/shared/i18n/locales/en/clusters.json @@ -0,0 +1,70 @@ +{ + "clusters": "Clusters", + "cluster": "Cluster", + "createCluster": "Create cluster", + "createPostgresCluster": "Create Postgres Cluster", + "clusterName": "Cluster name", + "creationTime": "Creation time", + "servers": "Servers", + "server": "Server", + "postgresVersion": "Postgres version", + "location": "Location", + "noPostgresClustersTitle": "No Postgres Clusters", + "noPostgresClustersLine1": "Deploy Postgres to supported cloud providers: AWS, GCP, Azure, DigitalOcean and Hetzner Cloud. All components are installed within your cloud account.\nOr Install on your existing resources, whether it's any other cloud or your own data center.\nTo get started, just click “{{createCluster}}” button.", + "selectDeploymentDestination": "Select deployment destination", + "clustersSearchPlaceholder": "Enter property name or value", + "selectCloudRegion": "Select cloud region", + "selectInstanceType": "Select instance type", + "numberOfInstances": "Number of instances", + "dataDiskStorage": "Data disk storage", + "sshPublicKey": "SSH public key", + "sshKey": "SSH key", + "sshKeyAuthDescription": "Connect to your services with an SSH key pair", + "passwordAuthDescription": "Connect to your services with an SSH user and password", + "description": "Description", + "sshKeyLocalMachinePlaceholder": "Paste your private SSH key here to access to the servers during deployment.\nEnsure that the corresponding public SSH key has been added to the ~/.ssh/authorized_keys file on all specified servers.", + "sshKeyCloudProviderPlaceholder": "Paste the SSH public keys here (one per line). These keys will be added to the database server's ~/.ssh/authorized_keys file.\nAt least one public key must be provided to ensure access to the server after deployment.", + "descriptionPlaceholder": "Optional. Here you can specify any additional information about the cluster or leave notes.", + "summary": "Summary", + "name": "Name", + "cloud": "Cloud", + "region": "Region", + "instanceType": "Instance type", + "estimatedMonthlyPrice": "Estimated monthly price", + "estimatedCostAdditionalInfo": "* Payment is made directly to the cloud provider.\nAn estimated cost is provided here. Please refer to the <0>official pricing page to confirm the actual costs.", + "yourOwn": "Your Own", + "machines": "Machines", + "yourOwnMachinesTooltip": "Use \"Your Own Machines\" to deploy the cluster on existing servers in another cloud provider or your own data center.", + "perServer": "per server", + "perDisk": "per disk", + "hostname": "Hostname", + "locationPlaceholder": "region/datacenter (optional)", + "databaseServers": "Database servers", + "authenticationMethod": "Authentication method", + "saveToConsole": "Save to console", + "clusterVipAddress": "Cluster VIP address", + "clusterVipAddressPlaceholder": "Optional. Specify the (unused) IP address here to provide a single entry point for client access to databases in the cluster. Not for cloud environments.", + "loadBalancers": "Load balancers", + "loadBalancing": "Load balancing", + "haproxyLoadBalancer": "HAProxy load balancer", + "haproxyLoadBalancerTooltip": "Deploy a HAProxy Load Balancer. This feature supports load balancing for read operations, facilitating effective scale-out strategies through the use of read-only replicas.", + "numberOfServers": "Number of servers", + "highAvailability": "High availability", + "highAvailabilityInfo": "*A minimum of 3 servers is required to ensure high availability.", + "host": "Host", + "role": "Role", + "state": "State", + "timeline": "Timeline", + "lagInMb": "Lag in MB", + "pendingRestart": "Pending restart", + "scheduledRestart": "Scheduled restart", + "tags": "Tags", + "connectionInfo": "Connection info", + "clusterInfo": "Cluster info", + "ipAddress": "IP Address", + "port": "Port", + "backups": "Backups", + "useDefinedSecret": "Use defined secret?", + "deleteClusterModalHeader": "Delete {{clusterName}}?", + "deleteClusterModalBody": "Are you sure you want to delete cluster \"{{clusterName}}\"?" +} diff --git a/console/ui/src/shared/i18n/locales/en/operations.json b/console/ui/src/shared/i18n/locales/en/operations.json new file mode 100644 index 000000000..6b63bc625 --- /dev/null +++ b/console/ui/src/shared/i18n/locales/en/operations.json @@ -0,0 +1,16 @@ +{ + "operations": "Operations", + "operation": "Operation", + "started": "Started", + "finished": "Finished", + "creationTime": "Creation time", + "type": "Type", + "showDetails": "Show details", + "lastDay": "Last day", + "lastWeek": "Last week", + "lastMonth": "Last month", + "lastThreeMonths": "Last three months", + "lastSixMonths": "Last six months", + "lastYear": "Last year", + "log": "Log" +} \ No newline at end of file diff --git a/console/ui/src/shared/i18n/locales/en/settings.json b/console/ui/src/shared/i18n/locales/en/settings.json new file mode 100644 index 000000000..6e5985dbd --- /dev/null +++ b/console/ui/src/shared/i18n/locales/en/settings.json @@ -0,0 +1,27 @@ +{ + "settings": "Settings", + "setting": "Setting", + "generalSettings": "General settings", + "environments": "Environments", + "environmentName": "Environment name", + "addEnvironment": "Add environment", + "secret": "Secret", + "secrets": "Secrets", + "proxyServer": "Proxy server", + "proxyServerInfo": "Specify your HTTP and HTTPS corporate proxy server settings below\nto enable package downloads during cluster deployment in environments without direct internet access (optional)", + "addSecret": "Add secret", + "secretType": "Secret type", + "secretName": "Secret name", + "addProject": "Add project", + "projectName": "Project name", + "settingsAwsSecretInfo": "Enter the access key to deploy the cluster to your AWS cloud provider account below. See the <0>official documentation for instructions on creating an access key.", + "settingsGcpSecretInfo": "Enter the service account content (in json or base64 format) to deploy the cluster to your Google Cloud provider account below. See the <0>official documentation for instructions on creating an service account key.", + "settingsDoSecretInfo": "Enter the API token to deploy the cluster to your DigitalOcean Cloud provider account below. See the <0>official documentation for instructions on creating an access token.", + "settingsAzureSecretInfo": "Enter the necessary details to deploy the cluster to your Azure Cloud provider account below. See the <0>official documentation for instructions on creating an service principal.", + "settingsHetznerSecretInfo": "Enter the API token to deploy the cluster to your Hetzner Cloud provider account below. See the <0>official documentation for instructions on creating an access token.", + "settingsSshKeySecretInfo": "Enter the contents of your private SSH key below to access the cluster servers via SSH. It is assumed that the corresponding public key has already been added to the servers.", + "settingsPasswordSecretInfo": "Enter the SSH username and password below to access the cluster servers. It is assumed that the user account, such as root or one with sudo privileges, has already been created on the servers.", + "settingsConfidentialDataStore": "All confidential data entered in these fields is stored in encrypted form.", + "sshPrivateKey": "SSH private key", + "month": "Month" +} \ No newline at end of file diff --git a/console/ui/src/shared/i18n/locales/en/shared.json b/console/ui/src/shared/i18n/locales/en/shared.json new file mode 100644 index 000000000..084c0a52b --- /dev/null +++ b/console/ui/src/shared/i18n/locales/en/shared.json @@ -0,0 +1,45 @@ +{ + "logout": "Logout", + "github": "Github repository", + "documentation": "Documentation", + "support": "Support", + "sponsor": "Sponsor", + "404Title": "Nothing to see here", + "404Text": "Page you are trying to open does not exist. You may have mistyped the address, or the page has been moved to another URL.\nIf you think this is an error contact support.", + "404ButtonText": "Take me back to home page", + "removeFromList": "Remove from list", + "refresh": "Refresh", + "overview": "Overview", + "cancel": "Cancel", + "save": "Save", + "status": "Status", + "id": "ID", + "environment": "Environment", + "actions": "Actions", + "user": "User", + "username": "Username", + "password": "Password", + "project": "Project", + "on": "On", + "off": "Off", + "login": "Login", + "token": "Token", + "enterTokenPlaceholder": "Enter token", + "amount": "Amount", + "name": "Name", + "type": "Type", + "created": "Created", + "updated": "Updated", + "used": "Used", + "delete": "Delete", + "selectSecret": "Select secret", + "addNewProject": "Add new project", + "projectName": "Project name", + "enterNewProjectName": "Enter new project name", + "description": "Description", + "yes": "Yes", + "no": "No", + "add": "Add", + "defaultTableSearchPlaceholder": "Enter property name or value", + "address": "Address" +} \ No newline at end of file diff --git a/console/ui/src/shared/i18n/locales/en/toasts.json b/console/ui/src/shared/i18n/locales/en/toasts.json new file mode 100644 index 000000000..18114a15c --- /dev/null +++ b/console/ui/src/shared/i18n/locales/en/toasts.json @@ -0,0 +1,18 @@ +{ + "clusterSuccessfullyCreated": "Cluster {{clusterName}} creation initiated. Please wait until deployment is complete", + "clusterSuccessfullyRemoved": "Cluster {{clusterName}} successfully removed", + "secretSuccessfullyCreated": "Secret {{secretName}} successfully created", + "secretSuccessfullyRemoved": "Secret {{secretName}} successfully removed", + "settingsSuccessfullyChanged": "Settings successfully changed", + "secretsSecretIsUsed_one": "The secret cannot be deleted because it is currently being used by the following cluster: {{clusterNames}}", + "secretsSecretIsUsed_other": "The secret cannot be deleted because it is currently being used by the following clusters: {{clusterNames}}", + "projectSuccessfullyCreated": "Project {{projectName}} successfully created", + "projectSuccessfullyRemoved": "Project {{projectName}} successfully removed", + "cannotRemoveActiveProject": "Cannot remove active project", + "environmentSuccessfullyCreated": "Environment {{environmentName}} successfully created", + "environmentSuccessfullyRemoved": "Environment {{environmentName}} successfully removed", + "serverSuccessfullyRemoved": "Server {{serverName}} successfully removed", + "invalidToken": "Token is not valid", + "valueCopiedToClipboard": "Value copied to clipboard", + "failedToCopyToClipboard": "Failed to copy to clipboard" +} \ No newline at end of file diff --git a/console/ui/src/shared/i18n/locales/en/validation.json b/console/ui/src/shared/i18n/locales/en/validation.json new file mode 100644 index 000000000..48e06eb72 --- /dev/null +++ b/console/ui/src/shared/i18n/locales/en/validation.json @@ -0,0 +1,5 @@ +{ + "requiredField": "Required field", + "clusterShouldHaveProperNaming": "Cluster name should have only letters, numbers, hyphens and have length equal or less than 24", + "shouldBeACorrectV4Ip": "The value should be a valid IPv4 address" +} diff --git a/console/ui/src/shared/lib/functions.ts b/console/ui/src/shared/lib/functions.ts new file mode 100644 index 000000000..804698608 --- /dev/null +++ b/console/ui/src/shared/lib/functions.ts @@ -0,0 +1,43 @@ +import { generatePath, resolvePath } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { format } from 'date-fns/format'; +import { isValid } from 'date-fns/isValid'; + +declare const window: any; + +/** + * Function generates absolute path that can be used for react-router "navigate" function. + * @param path - Absolute URN path. + * @param params - Additional params that will be substituted in URN dynamic values. + */ +export const generateAbsoluteRouterPath = (path: string, params?: Record) => + resolvePath(generatePath(path, params)); + +/** + * Function returns env variable passed to container or variable from .env* files. + * @param variableName - Name of a variable. + */ +export const getEnvVariable = (variableName: string) => window?._env_?.[variableName] ?? import.meta.env[variableName]; + +/** + * Function manages error event when performing request. + * @param e - Error event. + */ +export const handleRequestErrorCatch = (e) => { + console.error(e); + toast.error(e); +}; + +/** + * Function converts timestamp to easily readable string. + * @param timestamp - Timestamp to be converted. + */ +export const convertTimestampToReadableTime = (timestamp?: string) => + isValid(new Date(timestamp)) ? format(timestamp, 'MMM dd, yyyy, HH:mm:ss') : '-'; + +export const manageSortingOrder = ( + sorting: { + desc?: boolean; + id?: string; + }[], +) => (sorting?.desc ? `-${sorting?.id}` : sorting?.id); diff --git a/console/ui/src/shared/lib/hooks.tsx b/console/ui/src/shared/lib/hooks.tsx new file mode 100644 index 000000000..d25c0cb12 --- /dev/null +++ b/console/ui/src/shared/lib/hooks.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; + +/** + * Hook polls RTK query request every N seconds. + * @param request - RTK request to poll. + * @param pollingInterval - Number in milliseconds that represents polling interval. + * @param options - Different config options. + */ +export const useQueryPolling = (request: any, pollingInterval: number, options?: { stop?: boolean }) => { + const result = request(); + + useEffect(() => { + const polling = setInterval(() => result.refetch(), pollingInterval); + if (options?.stop?.toString() === 'true') clearInterval(polling); + return () => { + clearInterval(polling); + }; + }, [options]); + + return result; +}; + +/** + * Custom hook for copying value to clipboard. Returns copied value and function to copy value. + */ +export const useCopyToClipboard = (): [copiedText: string | null, copyFunction: (text: string) => Promise] => { + const { t } = useTranslation('toasts'); + const [copiedText, setCopiedText] = useState(null); + + const copyFunction = async (text) => { + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text); + } else { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + document.execCommand('copy'); + textArea.remove(); + } + setCopiedText(text); + toast.success(t('valueCopiedToClipboard')); + return true; + } catch (error) { + console.warn('Copy failed', error); + toast.error(t('failedToCopyToClipboard')); + setCopiedText(null); + return false; + } + }; + + return [copiedText, copyFunction]; +}; diff --git a/console/ui/src/shared/model/constants.ts b/console/ui/src/shared/model/constants.ts new file mode 100644 index 000000000..09db1a02b --- /dev/null +++ b/console/ui/src/shared/model/constants.ts @@ -0,0 +1,5 @@ +export const AUTHENTICATION_METHODS = Object.freeze({ + // changing names might break secrets POST request + SSH: 'ssh_key', + PASSWORD: 'password', +}); diff --git a/console/ui/src/shared/model/types.ts b/console/ui/src/shared/model/types.ts new file mode 100644 index 000000000..9dbbf4501 --- /dev/null +++ b/console/ui/src/shared/model/types.ts @@ -0,0 +1,6 @@ +import { MRT_Row, MRT_RowData } from 'material-react-table'; + +export interface TableRowActionsProps { + closeMenu: () => void; + row: MRT_Row; +} diff --git a/console/ui/src/shared/theme/theme.ts b/console/ui/src/shared/theme/theme.ts new file mode 100644 index 000000000..5cf939486 --- /dev/null +++ b/console/ui/src/shared/theme/theme.ts @@ -0,0 +1,36 @@ +import { createTheme } from '@mui/material'; +import { blue } from '@mui/material/colors'; +import { enUS } from '@mui/material/locale'; + +declare module '@mui/material/styles' { + interface PaletteColor { + lighter10?: string; + } + + interface SimplePaletteColorOptions { + lighter10?: string; + } +} + +const theme = createTheme( + { + palette: { + primary: { + main: blue[800], + lighter10: '#0D8CE91A', + }, + }, + components: { + MuiAppBar: { + styleOverrides: { + colorPrimary: { + backgroundColor: '#F6F8FA', + }, + }, + }, + }, + }, + enUS, +); + +export default theme; diff --git a/console/ui/src/shared/ui/copy-icon/assets/copyIcon.svg b/console/ui/src/shared/ui/copy-icon/assets/copyIcon.svg new file mode 100644 index 000000000..a45972213 --- /dev/null +++ b/console/ui/src/shared/ui/copy-icon/assets/copyIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/console/ui/src/shared/ui/copy-icon/index.ts b/console/ui/src/shared/ui/copy-icon/index.ts new file mode 100644 index 000000000..d870f7c87 --- /dev/null +++ b/console/ui/src/shared/ui/copy-icon/index.ts @@ -0,0 +1,3 @@ +import CopyIcon from '@shared/ui/copy-icon/ui'; + +export default CopyIcon; diff --git a/console/ui/src/shared/ui/copy-icon/model/types.ts b/console/ui/src/shared/ui/copy-icon/model/types.ts new file mode 100644 index 000000000..1cf47b13f --- /dev/null +++ b/console/ui/src/shared/ui/copy-icon/model/types.ts @@ -0,0 +1,3 @@ +export interface CopyIconProps { + valueToCopy?: string; +} diff --git a/console/ui/src/shared/ui/copy-icon/ui/index.tsx b/console/ui/src/shared/ui/copy-icon/ui/index.tsx new file mode 100644 index 000000000..6eb304990 --- /dev/null +++ b/console/ui/src/shared/ui/copy-icon/ui/index.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { CopyIconProps } from '@shared/ui/copy-icon/model/types.ts'; +import { useCopyToClipboard } from '@shared/lib/hooks.tsx'; +import CopyValueIcon from '@shared/ui/copy-icon/assets/copyIcon.svg?react'; +import { Box } from '@mui/material'; + +const CopyIcon: FC = ({ valueToCopy }) => { + const [_, copyFunction] = useCopyToClipboard(); + + return ( + copyFunction(valueToCopy)} sx={{ cursor: 'pointer' }}> + + + ); +}; + +export default CopyIcon; diff --git a/console/ui/src/shared/ui/default-form-buttons/index.ts b/console/ui/src/shared/ui/default-form-buttons/index.ts new file mode 100644 index 000000000..3d7cfb8e4 --- /dev/null +++ b/console/ui/src/shared/ui/default-form-buttons/index.ts @@ -0,0 +1,3 @@ +import DefaultFormButtons from '@shared/ui/default-form-buttons/ui'; + +export default DefaultFormButtons; diff --git a/console/ui/src/shared/ui/default-form-buttons/model/types.ts b/console/ui/src/shared/ui/default-form-buttons/model/types.ts new file mode 100644 index 000000000..fa08836ff --- /dev/null +++ b/console/ui/src/shared/ui/default-form-buttons/model/types.ts @@ -0,0 +1,10 @@ +import { ReactElement } from 'react'; + +export interface DefaultFormButtonsProps { + isDisabled?: boolean; + isSubmitting?: boolean; + submitButtonLabel?: string; + cancelButtonLabel?: string; + cancelHandler: () => void; + children?: ReactElement; +} diff --git a/console/ui/src/shared/ui/default-form-buttons/ui/index.tsx b/console/ui/src/shared/ui/default-form-buttons/ui/index.tsx new file mode 100644 index 000000000..93dbe5f77 --- /dev/null +++ b/console/ui/src/shared/ui/default-form-buttons/ui/index.tsx @@ -0,0 +1,51 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from '@emotion/styled'; +import { LoadingButton } from '@mui/lab'; +import { DefaultFormButtonsProps } from '@shared/ui/default-form-buttons/model/types.ts'; +import { CircularProgress } from '@mui/material'; + +const StyledDefaultFormButtons = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +const StyledStandardButtons = styled.div` + display: grid; + grid-template: 1fr / repeat(2, auto); + grid-column-gap: 16px; + width: fit-content; +`; + +const DefaultFormButtons: FC = ({ + isDisabled = false, + isSubmitting = false, + cancelHandler, + submitButtonLabel, + cancelButtonLabel, + children, +}) => { + const { t } = useTranslation('shared'); + + return ( + + + } + type="submit"> + {submitButtonLabel ?? t('save')} + + + {cancelButtonLabel ?? t('cancel')} + + + {children} + + ); +}; + +export default DefaultFormButtons; diff --git a/console/ui/src/shared/ui/default-table/index.ts b/console/ui/src/shared/ui/default-table/index.ts new file mode 100644 index 000000000..a7eeb4844 --- /dev/null +++ b/console/ui/src/shared/ui/default-table/index.ts @@ -0,0 +1,3 @@ +import DefaultTable from '@shared/ui/default-table/ui'; + +export default DefaultTable; diff --git a/console/ui/src/shared/ui/default-table/ui/index.tsx b/console/ui/src/shared/ui/default-table/ui/index.tsx new file mode 100644 index 000000000..96de73e08 --- /dev/null +++ b/console/ui/src/shared/ui/default-table/ui/index.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react'; +import { MaterialReactTable, MRT_RowData, MRT_TableOptions, useMaterialReactTable } from 'material-react-table'; +import { PAGINATION_LIMIT_OPTIONS } from '@shared/config/constants.ts'; +import { useTranslation } from 'react-i18next'; + +/** + * Common table with default styles. + * @param tableConfig - Object with additional table configuration. + * @constructor + */ +const DefaultTable: FC = ({ tableConfig }: { tableConfig: MRT_TableOptions }) => { + const { t } = useTranslation('shared'); + + const table = useMaterialReactTable({ + muiPaginationProps: { + rowsPerPageOptions: PAGINATION_LIMIT_OPTIONS, + }, + muiSearchTextFieldProps: { + placeholder: t('defaultTableSearchPlaceholder'), + sx: { minWidth: '300px' }, + }, + muiTableHeadCellProps: { + sx: { + backgroundColor: '#F6F8FA', + }, + }, + displayColumnDefOptions: { + 'mrt-row-select': { + visibleInShowHideMenu: false, + }, + 'mrt-row-actions': { + visibleInShowHideMenu: false, + }, + }, + layoutMode: 'grid', + positionActionsColumn: 'last', + positionGlobalFilter: 'left', + ...tableConfig, + }); + + return ; +}; + +export default DefaultTable; diff --git a/console/ui/src/shared/ui/info-card-body/index.ts b/console/ui/src/shared/ui/info-card-body/index.ts new file mode 100644 index 000000000..7a4ccea81 --- /dev/null +++ b/console/ui/src/shared/ui/info-card-body/index.ts @@ -0,0 +1,3 @@ +import InfoCardBody from '@shared/ui/info-card-body/ui'; + +export default InfoCardBody; diff --git a/console/ui/src/shared/ui/info-card-body/model/types.ts b/console/ui/src/shared/ui/info-card-body/model/types.ts new file mode 100644 index 000000000..6b9017cf5 --- /dev/null +++ b/console/ui/src/shared/ui/info-card-body/model/types.ts @@ -0,0 +1,8 @@ +import { ReactNode } from 'react'; + +export interface InfoCardBodyProps { + config: { + title: string; + children: ReactNode; + }[]; +} diff --git a/console/ui/src/shared/ui/info-card-body/ui/index.tsx b/console/ui/src/shared/ui/info-card-body/ui/index.tsx new file mode 100644 index 000000000..b1b485437 --- /dev/null +++ b/console/ui/src/shared/ui/info-card-body/ui/index.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react'; +import { Divider, Stack, Typography } from '@mui/material'; +import { InfoCardBodyProps } from '@shared/ui/info-card-body/model/types.ts'; + +/** + * Component renders body of a different summary and overview cards. + * Recommended to use inside all similar looking card bodies. + * @param config - Config with data to render. + * @constructor + */ +const InfoCardBody: FC = ({ config }) => { + return ( + + {config.map(({ title, children }, index) => ( + + + {title} + + {children} + {index < config.length - 1 ? : null} + + ))} + + ); +}; + +export default InfoCardBody; diff --git a/console/ui/src/shared/ui/selectable-box/index.ts b/console/ui/src/shared/ui/selectable-box/index.ts new file mode 100644 index 000000000..8c3b67b74 --- /dev/null +++ b/console/ui/src/shared/ui/selectable-box/index.ts @@ -0,0 +1,3 @@ +import SelectableBox from '@shared/ui/selectable-box/ui'; + +export default SelectableBox; diff --git a/console/ui/src/shared/ui/selectable-box/model/types.ts b/console/ui/src/shared/ui/selectable-box/model/types.ts new file mode 100644 index 000000000..fd5eb3171 --- /dev/null +++ b/console/ui/src/shared/ui/selectable-box/model/types.ts @@ -0,0 +1,8 @@ +import { SxProps } from '@mui/material'; +import { ReactNode } from 'react'; + +export interface ClusterFormSelectableBoxProps extends ReactNode { + children?: ReactNode; + isActive?: boolean; + sx?: SxProps; +} diff --git a/console/ui/src/shared/ui/selectable-box/ui/index.tsx b/console/ui/src/shared/ui/selectable-box/ui/index.tsx new file mode 100644 index 000000000..cd18565b6 --- /dev/null +++ b/console/ui/src/shared/ui/selectable-box/ui/index.tsx @@ -0,0 +1,21 @@ +import { FC } from 'react'; +import { ClusterFormSelectableBoxProps } from '@shared/ui/selectable-box/model/types.ts'; +import theme from '@shared/theme/theme.ts'; +import { Box } from '@mui/material'; + +const SelectableBox: FC = ({ isActive, children, sx, ...props }) => { + return ( + + {children} + + ); +}; + +export default SelectableBox; diff --git a/console/ui/src/shared/ui/settings-add-entity/model/constants.ts b/console/ui/src/shared/ui/settings-add-entity/model/constants.ts new file mode 100644 index 000000000..d95c1f9cc --- /dev/null +++ b/console/ui/src/shared/ui/settings-add-entity/model/constants.ts @@ -0,0 +1,4 @@ +export const ADD_ENTITY_FORM_NAMES = Object.freeze({ + NAME: 'name', + DESCRIPTION: 'description', +}); diff --git a/console/ui/src/shared/ui/settings-add-entity/model/types.ts b/console/ui/src/shared/ui/settings-add-entity/model/types.ts new file mode 100644 index 000000000..3eaf490cf --- /dev/null +++ b/console/ui/src/shared/ui/settings-add-entity/model/types.ts @@ -0,0 +1,13 @@ +import { ADD_ENTITY_FORM_NAMES } from '@shared/ui/settings-add-entity/model/constants.ts'; + +export interface AddEntityFormValues { + [ADD_ENTITY_FORM_NAMES.NAME]: string; + [ADD_ENTITY_FORM_NAMES.NAME]: string; +} + +export interface SettingsAddEntityProps { + buttonLabel: string; + submitButtonLabel: string; + isLoading?: boolean; + submitTrigger: (values: AddEntityFormValues) => void; +} diff --git a/console/ui/src/shared/ui/settings-add-entity/model/validation.ts b/console/ui/src/shared/ui/settings-add-entity/model/validation.ts new file mode 100644 index 000000000..f78a86fc7 --- /dev/null +++ b/console/ui/src/shared/ui/settings-add-entity/model/validation.ts @@ -0,0 +1,9 @@ +import { TFunction } from 'i18next'; +import * as yup from 'yup'; +import { ADD_ENTITY_FORM_NAMES } from '@shared/ui/settings-add-entity/model/constants.ts'; + +export const AddEntityFormSchema = (t: TFunction) => + yup.object({ + [ADD_ENTITY_FORM_NAMES.NAME]: yup.string().required(t('requiredField', { ns: 'validation' })), + [ADD_ENTITY_FORM_NAMES.DESCRIPTION]: yup.string(), + }); diff --git a/console/ui/src/shared/ui/settings-add-entity/ui/index.tsx b/console/ui/src/shared/ui/settings-add-entity/ui/index.tsx new file mode 100644 index 000000000..0a3c12713 --- /dev/null +++ b/console/ui/src/shared/ui/settings-add-entity/ui/index.tsx @@ -0,0 +1,120 @@ +import React, { FC, useState } from 'react'; +import { Button, Card, CircularProgress, Modal, Stack, TextField, Typography } from '@mui/material'; +import { Controller, useForm } from 'react-hook-form'; +import { LoadingButton } from '@mui/lab'; +import AddBoxOutlinedIcon from '@mui/icons-material/AddBoxOutlined'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { AddEntityFormValues, SettingsAddEntityProps } from '@shared/ui/settings-add-entity/model/types.ts'; +import { AddEntityFormSchema } from '@shared/ui/settings-add-entity/model/validation.ts'; +import { ADD_ENTITY_FORM_NAMES } from '@shared/ui/settings-add-entity/model/constants.ts'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import { useTranslation } from 'react-i18next'; + +const SettingsAddEntity: FC = ({ + buttonLabel, + headerLabel, + submitButtonLabel, + nameLabel, + isLoading, + submitTrigger, +}) => { + const { t } = useTranslation(['settings', 'shared']); + const [isModalOpen, setIsModalOpen] = useState(false); + + const handleModalOpenState = (isOpen: boolean) => () => setIsModalOpen(isOpen); + + const { + control, + handleSubmit, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + mode: 'all', + resolver: yupResolver(AddEntityFormSchema(t)), + }); + + const onSubmit = async (values: AddEntityFormValues) => { + try { + await submitTrigger(values); + setIsModalOpen(false); + } catch (e) { + handleRequestErrorCatch(e); + } + }; + + return ( + <> + + +
+ + + + {headerLabel ?? t('add', { ns: 'shared' })} + + + ( + + )} + /> + + + ( + + )} + /> + + } + loading={isSubmitting || isLoading}> + {submitButtonLabel ?? t('add')} + + + +
+
+ + ); +}; + +export default SettingsAddEntity; diff --git a/console/ui/src/shared/ui/slider-box/index.ts b/console/ui/src/shared/ui/slider-box/index.ts new file mode 100644 index 000000000..245befdf6 --- /dev/null +++ b/console/ui/src/shared/ui/slider-box/index.ts @@ -0,0 +1,3 @@ +import ClusterSliderBox from '@shared/ui/slider-box/ui'; + +export default ClusterSliderBox; diff --git a/console/ui/src/shared/ui/slider-box/lib/functions.ts b/console/ui/src/shared/ui/slider-box/lib/functions.ts new file mode 100644 index 000000000..3f9bb095e --- /dev/null +++ b/console/ui/src/shared/ui/slider-box/lib/functions.ts @@ -0,0 +1,17 @@ +import { GenerateMarkType, GenerateSliderMarksType } from '@shared/ui/slider-box/model/types.ts'; + +const generateMark: GenerateMarkType = (value, marksAdditionalLabel) => ({ + value, + label: `${value} ${marksAdditionalLabel}`, +}); + +export const generateSliderMarks: GenerateSliderMarksType = (min, max, amount, marksAdditionalLabel) => { + const step = (max - min) / (amount - 1); + const marksArray = []; + + for (let i = min; i < max + step; i += step) { + marksArray.push(generateMark(Math.trunc(i), marksAdditionalLabel)); + } + + return marksArray; +}; diff --git a/console/ui/src/shared/ui/slider-box/model/types.ts b/console/ui/src/shared/ui/slider-box/model/types.ts new file mode 100644 index 000000000..d8af2950e --- /dev/null +++ b/console/ui/src/shared/ui/slider-box/model/types.ts @@ -0,0 +1,29 @@ +import { ReactElement } from 'react'; + +export interface SliderBoxProps { + amount: number; + changeAmount: (...event: any[]) => void; + icon?: ReactElement; + unit?: string; + min: number; + max: number; + marks?: { label: unknown; value: unknown }[]; + marksAmount?: number; + marksAdditionalLabel?: string; + step?: number | null; + error?: object; + limitMin?: boolean; + limitMax?: boolean; +} + +export type GenerateMarkType = (value: number, marksAdditionalLabel: string) => { label: string; value: string }; + +export type GenerateSliderMarksType = ( + min: number, + max: number, + amount: number, + marksAdditionalLabel: string, +) => { + label: string; + value: string; +}[]; diff --git a/console/ui/src/shared/ui/slider-box/ui/index.tsx b/console/ui/src/shared/ui/slider-box/ui/index.tsx new file mode 100644 index 000000000..15bbb190b --- /dev/null +++ b/console/ui/src/shared/ui/slider-box/ui/index.tsx @@ -0,0 +1,67 @@ +import { FC } from 'react'; +import { Box, Slider, TextField, Typography } from '@mui/material'; +import { SliderBoxProps } from '@shared/ui/slider-box/model/types.ts'; + +import { generateSliderMarks } from '@shared/ui/slider-box/lib/functions.ts'; + +const ClusterSliderBox: FC = ({ + amount, + changeAmount, + unit, + icon, + min = 1, + max, + marks, + marksAmount, + marksAdditionalLabel = '', + step, + error, + limitMin = true, + limitMax, +}) => { + const onChange = (e) => { + const { value } = e.target; + + if (/^\d*$/.test(value)) changeAmount(value < min && limitMin ? min : value > max && limitMax ? max : value); + }; + + return ( + + + {icon} + + {unit} + + + + + + ); +}; + +export default ClusterSliderBox; diff --git a/console/ui/src/shared/ui/spinner/index.ts b/console/ui/src/shared/ui/spinner/index.ts new file mode 100644 index 000000000..14e55f442 --- /dev/null +++ b/console/ui/src/shared/ui/spinner/index.ts @@ -0,0 +1,3 @@ +import Spinner from '@shared/ui/spinner/ui'; + +export default Spinner; diff --git a/console/ui/src/shared/ui/spinner/ui/index.tsx b/console/ui/src/shared/ui/spinner/ui/index.tsx new file mode 100644 index 000000000..2e61ac1b4 --- /dev/null +++ b/console/ui/src/shared/ui/spinner/ui/index.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react'; +import { CircularProgress, Stack } from '@mui/material'; + +const Spinner: FC = () => { + return ( + + + + ); +}; + +export default Spinner; diff --git a/console/ui/src/widgets/cluster-form/index.ts b/console/ui/src/widgets/cluster-form/index.ts new file mode 100644 index 000000000..24ad7a10d --- /dev/null +++ b/console/ui/src/widgets/cluster-form/index.ts @@ -0,0 +1,3 @@ +import ClusterForm from '@widgets/cluster-form/ui'; + +export default ClusterForm; diff --git a/console/ui/src/widgets/cluster-form/model/constants.ts b/console/ui/src/widgets/cluster-form/model/constants.ts new file mode 100644 index 000000000..3aeb2d1f0 --- /dev/null +++ b/console/ui/src/widgets/cluster-form/model/constants.ts @@ -0,0 +1,36 @@ +export const numberOfInstances = [1, 3, 7, 15, 32]; +export const dataDiskStorage = [10, 100, 500, 1000, 2000, 16000]; + +const CLUSTER_CLOUD_PROVIDER_FIELD_NAMES = Object.freeze({ + REGION: 'region', + REGION_CONFIG: 'regionConfig', + INSTANCE_TYPE: 'instanceType', + INSTANCE_CONFIG: 'instanceConfig', + INSTANCES_AMOUNT: 'instancesAmount', + STORAGE_AMOUNT: 'storageAmount', + SSH_PUBLIC_KEY: 'sshPublicKey', +}); + +const CLUSTER_LOCAL_MACHINE_FIELD_NAMES = Object.freeze({ + DATABASE_SERVERS: 'databaseServers', + HOSTNAME: 'hostname', + IP_ADDRESS: 'ipAddress', + LOCATION: 'location', + AUTHENTICATION_METHOD: 'authenticationMethod', + SECRET_KEY_NAME: 'secretKeyName', + AUTHENTICATION_IS_SAVE_TO_CONSOLE: 'authenticationSaveToConsole', + CLUSTER_VIP_ADDRESS: 'clusterVIPAddress', + IS_HAPROXY_LOAD_BALANCER: 'isHaproxyLoadBalancer', + IS_USE_DEFINED_SECRET: 'isUseDefinedSecret', +}); + +export const CLUSTER_FORM_FIELD_NAMES = Object.freeze({ + PROVIDER: 'provider', + ENVIRONMENT_ID: 'environment', + CLUSTER_NAME: 'clusterName', + DESCRIPTION: 'description', + POSTGRES_VERSION: 'postgresVersion', + SECRET_ID: 'secretId', + ...CLUSTER_CLOUD_PROVIDER_FIELD_NAMES, + ...CLUSTER_LOCAL_MACHINE_FIELD_NAMES, +}); diff --git a/console/ui/src/widgets/cluster-form/model/types.ts b/console/ui/src/widgets/cluster-form/model/types.ts new file mode 100644 index 000000000..f0f9afaa3 --- /dev/null +++ b/console/ui/src/widgets/cluster-form/model/types.ts @@ -0,0 +1,13 @@ +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; + +export interface ClusterFormRegionConfigBoxProps { + name: string; + place: string; + isActive: boolean; +} + +export interface ClusterDatabaseServer { + [CLUSTER_FORM_FIELD_NAMES.HOSTNAME]: string; + [CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS]: string; + [CLUSTER_FORM_FIELD_NAMES.LOCATION]: string; +} diff --git a/console/ui/src/widgets/cluster-form/model/validation.ts b/console/ui/src/widgets/cluster-form/model/validation.ts new file mode 100644 index 000000000..4c7782d5e --- /dev/null +++ b/console/ui/src/widgets/cluster-form/model/validation.ts @@ -0,0 +1,203 @@ +import { TFunction } from 'i18next'; +import * as yup from 'yup'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { PROVIDERS } from '@shared/config/constants.ts'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; +import ipRegex from 'ip-regex'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; + +const cloudFormSchema = (t: TFunction) => + yup.object({ + [CLUSTER_FORM_FIELD_NAMES.REGION]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code !== PROVIDERS.LOCAL ? yup.string().required() : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code !== PROVIDERS.LOCAL + ? yup + .object({ + code: yup.string(), + location: yup.string(), + }) + .required() + : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_TYPE]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code !== PROVIDERS.LOCAL ? yup.string().required() : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code !== PROVIDERS.LOCAL + ? yup + .object({ + code: yup.string(), + cpu: yup.number(), + currency: yup.string(), + price_hourly: yup.number(), + price_monthly: yup.number(), + ram: yup.number(), + }) + .required() + : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code !== PROVIDERS.LOCAL ? yup.number().required() : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code !== PROVIDERS.LOCAL ? yup.number().required() : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.SSH_PUBLIC_KEY]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code !== PROVIDERS.LOCAL + ? yup.string().required(t('requiredField', { ns: 'validation' })) + : schema.notRequired(), + ), + }); + +export const localFormSchema = (t: TFunction) => + yup.object({ + [CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code === PROVIDERS.LOCAL + ? yup.array( + yup.object({ + [CLUSTER_FORM_FIELD_NAMES.HOSTNAME]: yup.string().required(t('requiredField', { ns: 'validation' })), + [CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS]: yup + .string() + .required(t('requiredField', { ns: 'validation' })) + .test('should be a correct IP', t('shouldBeACorrectV4Ip', { ns: 'validation' }), (value) => + ipRegex.v4({ exact: true }).test(value), + ), + [CLUSTER_FORM_FIELD_NAMES.LOCATION]: yup.string(), + }), + ) + : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code === PROVIDERS.LOCAL ? yup.string().required() : schema.notRequired(), + ), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SSH_PRIVATE_KEY]: yup + .mixed() + .when( + [CLUSTER_FORM_FIELD_NAMES.PROVIDER, CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD], + ([provider, authenticationMethod], schema) => + provider?.code === PROVIDERS.LOCAL && authenticationMethod === AUTHENTICATION_METHODS.SSH + ? yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET, ([isUseDefinedSecret], schema) => + !isUseDefinedSecret + ? yup.string().required(t('requiredField', { ns: 'validation' })) + : schema.notRequired(), + ) + : schema.notRequired(), + ), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.USERNAME]: yup + .mixed() + .when( + [CLUSTER_FORM_FIELD_NAMES.PROVIDER, CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD], + ([provider, authenticationMethod], schema) => + provider?.code === PROVIDERS.LOCAL && authenticationMethod === AUTHENTICATION_METHODS.PASSWORD + ? yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET, ([isUseDefinedSecret], schema) => + !isUseDefinedSecret + ? yup.string().required(t('requiredField', { ns: 'validation' })) + : schema.notRequired(), + ) + : schema.notRequired(), + ), + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.PASSWORD]: yup + .mixed() + .when( + [CLUSTER_FORM_FIELD_NAMES.PROVIDER, CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD], + ([provider, authenticationMethod], schema) => + provider?.code === PROVIDERS.LOCAL && authenticationMethod === AUTHENTICATION_METHODS.PASSWORD + ? yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET, ([isUseDefinedSecret], schema) => + !isUseDefinedSecret + ? yup.string().required(t('requiredField', { ns: 'validation' })) + : schema.notRequired(), + ) + : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_IS_SAVE_TO_CONSOLE]: yup + .mixed() + .when( + [CLUSTER_FORM_FIELD_NAMES.PROVIDER, CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET], + ([provider, isUseDefinedSecret], schema) => + provider?.code === PROVIDERS.LOCAL && !isUseDefinedSecret ? yup.boolean() : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.SECRET_KEY_NAME]: yup + .mixed() + .when( + [CLUSTER_FORM_FIELD_NAMES.PROVIDER, CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_IS_SAVE_TO_CONSOLE], + ([provider, isSaveToConsole], schema) => + provider?.code === PROVIDERS.LOCAL && isSaveToConsole + ? yup.string().required(t('requiredField', { ns: 'validation' })) + : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.CLUSTER_VIP_ADDRESS]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code === PROVIDERS.LOCAL + ? yup + .string() + .test( + 'should be a correct VIP address', + t('shouldBeACorrectV4Ip', { ns: 'validation' }), + (value) => !value || ipRegex.v4({ exact: true }).test(value), + ) + : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.IS_HAPROXY_LOAD_BALANCER]: yup + .mixed() + .when(CLUSTER_FORM_FIELD_NAMES.PROVIDER, ([provider], schema) => + provider?.code === PROVIDERS.LOCAL ? yup.boolean() : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET]: yup + .mixed() + .when([CLUSTER_FORM_FIELD_NAMES.PROVIDER], ([provider], schema) => + provider?.code === PROVIDERS.LOCAL ? yup.boolean().optional() : schema.notRequired(), + ), + [CLUSTER_FORM_FIELD_NAMES.SECRET_ID]: yup + .mixed() + .when( + [CLUSTER_FORM_FIELD_NAMES.PROVIDER, CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET], + ([provider, isUseDefinedSecret], schema) => + provider?.code === PROVIDERS.LOCAL && isUseDefinedSecret + ? yup.string().required(t('requiredField', { ns: 'validation' })) + : schema.notRequired(), + ), + }); + +export const ClusterFormSchema = (t: TFunction) => + yup + .object({ + [CLUSTER_FORM_FIELD_NAMES.PROVIDER]: yup.object().required(), + [CLUSTER_FORM_FIELD_NAMES.ENVIRONMENT_ID]: yup.number(), + [CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME]: yup + .string() + .test('clusters should have proper naming', t('clusterShouldHaveProperNaming', { ns: 'validation' }), (value) => + value.match(/^[a-z0-9][a-z0-9-]{0,23}$/g), + ) + .required(t('requiredField', { ns: 'validation' })), + [CLUSTER_FORM_FIELD_NAMES.DESCRIPTION]: yup.string(), + [CLUSTER_FORM_FIELD_NAMES.POSTGRES_VERSION]: yup.number().required(t('requiredField', { ns: 'validation' })), + }) + .concat(cloudFormSchema(t)) + .concat(localFormSchema(t)); diff --git a/console/ui/src/widgets/cluster-form/ui/ClusterFormCloudProviderFormPart.tsx b/console/ui/src/widgets/cluster-form/ui/ClusterFormCloudProviderFormPart.tsx new file mode 100644 index 000000000..32fe3e026 --- /dev/null +++ b/console/ui/src/widgets/cluster-form/ui/ClusterFormCloudProviderFormPart.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import ClusterFormRegionBlock from '@entities/cluster-form-cloud-region-block'; +import ClusterFormInstancesBlock from '@entities/cluster-form-instances-block'; +import InstancesAmountBlock from '@entities/cluster-form-instances-amount-block'; +import StorageBlock from '@entities/storage-block'; +import ClusterFormSshKeyBlock from '@entities/ssh-key-block'; + +const ClusterFormCloudProviderFormPart: FC = () => ( + <> + + + + + + +); + +export default ClusterFormCloudProviderFormPart; diff --git a/console/ui/src/widgets/cluster-form/ui/ClusterFormLocalMachineFormPart.tsx b/console/ui/src/widgets/cluster-form/ui/ClusterFormLocalMachineFormPart.tsx new file mode 100644 index 000000000..5ae5dcc61 --- /dev/null +++ b/console/ui/src/widgets/cluster-form/ui/ClusterFormLocalMachineFormPart.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import DatabaseServersBlock from '@entities/database-servers-block'; +import AuthenticationMethodFormBlock from '@entities/authentification-method-form-block'; +import VipAddressBlock from '@entities/vip-address-block'; +import LoadBalancersBlock from '@entities/load-balancers-block'; + +const ClusterFormLocalMachineFormPart: FC = () => ( + <> + + + + + +); + +export default ClusterFormLocalMachineFormPart; diff --git a/console/ui/src/widgets/cluster-form/ui/ClusterFormRegionConfigBox.tsx b/console/ui/src/widgets/cluster-form/ui/ClusterFormRegionConfigBox.tsx new file mode 100644 index 000000000..2dd7cdfd5 --- /dev/null +++ b/console/ui/src/widgets/cluster-form/ui/ClusterFormRegionConfigBox.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react'; +import SelectableBox from '@shared/ui/selectable-box'; +import { Box, Typography } from '@mui/material'; +import FlagsIcon from '@assets/flagIcon.svg?react'; +import { ClusterFormRegionConfigBoxProps } from '@widgets/cluster-form/model/types.ts'; + +const ClusterFormRegionConfigBox: FC = ({ name, place, isActive, ...props }) => { + return ( + + + {name} + + + +   + {place} + + + ); +}; + +export default ClusterFormRegionConfigBox; diff --git a/console/ui/src/widgets/cluster-form/ui/index.tsx b/console/ui/src/widgets/cluster-form/ui/index.tsx new file mode 100644 index 000000000..ee8cdb662 --- /dev/null +++ b/console/ui/src/widgets/cluster-form/ui/index.tsx @@ -0,0 +1,210 @@ +import React, { useLayoutEffect, useRef, useState } from 'react'; +import ProvidersBlock from '@entities/providers-block'; +import ClusterFormEnvironmentBlock from '@entities/cluster-form-environment-block'; +import ClusterNameBox from '@entities/cluster-form-cluster-name-block'; +import ClusterDescriptionBlock from '@entities/cluster-description-block'; +import PostgresVersionBox from '@entities/postgres-version-block'; +import DefaultFormButtons from '@shared/ui/default-form-buttons'; +import { FormProvider, useForm } from 'react-hook-form'; +import { generateAbsoluteRouterPath, handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { useGetExternalDeploymentsQuery } from '@shared/api/api/deployments.ts'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { PROVIDERS } from '@shared/config/constants.ts'; +import ClusterFormCloudProviderFormPart from '@widgets/cluster-form/ui/ClusterFormCloudProviderFormPart.tsx'; +import ClusterFormLocalMachineFormPart from '@widgets/cluster-form/ui/ClusterFormLocalMachineFormPart.tsx'; +import { useGetClustersDefaultNameQuery, usePostClustersMutation } from '@shared/api/api/clusters.ts'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import { Stack } from '@mui/material'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { ClusterFormSchema } from '@widgets/cluster-form/model/validation.ts'; +import ClusterSummary from '@widgets/cluster-summary'; +import ClusterSecretModal from '@features/cluster-secret-modal'; +import { useGetPostgresVersionsQuery } from '@shared/api/api/other.ts'; +import { useGetEnvironmentsQuery } from '@shared/api/api/environments.ts'; +import { mapFormValuesToRequestFields } from '@features/cluster-secret-modal/lib/functions.ts'; +import { toast } from 'react-toastify'; +import { AUTHENTICATION_METHODS } from '@shared/model/constants.ts'; +import { ClusterFormValues } from '@features/cluster-secret-modal/model/types.ts'; +import { useGetSecretsQuery, usePostSecretsMutation } from '@shared/api/api/secrets.ts'; +import { getSecretBodyFromValues } from '@entities/secret-form-block/lib/functions.ts'; +import { SECRET_MODAL_CONTENT_FORM_FIELD_NAMES } from '@entities/secret-form-block/model/constants.ts'; +import Spinner from '@shared/ui/spinner'; + +const ClusterForm: React.FC = () => { + const { t } = useTranslation(['clusters', 'validation', 'toasts']); + const navigate = useNavigate(); + const createSecretResultRef = useRef(null); // ref is used for case when user saves secret and uses its ID to create cluster + + const [isResetting, setIsResetting] = useState(false); + + const currentProject = useAppSelector(selectCurrentProject); + + const [addSecretTrigger, addSecretTriggerState] = usePostSecretsMutation(); + const [addClusterTrigger, addClusterTriggerState] = usePostClustersMutation(); + + const deployments = useGetExternalDeploymentsQuery({ offset: 0, limit: 999_999_999 }); + const environments = useGetEnvironmentsQuery({ offset: 0, limit: 999_999_999 }); + const postgresVersions = useGetPostgresVersionsQuery(); + const clusterName = useGetClustersDefaultNameQuery(); + + const methods = useForm({ + mode: 'all', + resolver: yupResolver(ClusterFormSchema(t)), + defaultValues: { + [CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT]: 3, + [CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD]: AUTHENTICATION_METHODS.SSH, + [CLUSTER_FORM_FIELD_NAMES.IS_USE_DEFINED_SECRET]: false, + [CLUSTER_FORM_FIELD_NAMES.SECRET_ID]: '', + [CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]: Array(3) + .fill(0) + .map(() => ({ + [CLUSTER_FORM_FIELD_NAMES.HOSTNAME]: '', + [CLUSTER_FORM_FIELD_NAMES.IP_ADDRESS]: '', + [CLUSTER_FORM_FIELD_NAMES.LOCATION]: '', + })), + }, + }); + + const watchProvider = methods.watch(CLUSTER_FORM_FIELD_NAMES.PROVIDER); + + const secrets = useGetSecretsQuery({ type: watchProvider?.code, projectId: currentProject }); + + useLayoutEffect(() => { + if (deployments.isFetching || postgresVersions.isFetching || environments.isFetching || clusterName.isFetching) + setIsResetting(true); + if (deployments.data?.data && postgresVersions.data?.data && environments.data?.data && clusterName.data) { + // eslint-disable-next-line @typescript-eslint/require-await + const resetForm = async () => { + // sync function will result in form values setting error + const providers = deployments.data.data; + methods.reset((values) => ({ + ...values, + [CLUSTER_FORM_FIELD_NAMES.PROVIDER]: providers?.[0], + [CLUSTER_FORM_FIELD_NAMES.REGION]: providers?.[0]?.cloud_regions[0]?.code, + [CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG]: providers?.[0]?.cloud_regions[0]?.datacenters?.[0], + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_TYPE]: 'small', + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]: providers?.[0]?.instance_types?.small?.[0], + [CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT]: 100, + [CLUSTER_FORM_FIELD_NAMES.POSTGRES_VERSION]: postgresVersions.data?.data?.at(-1)?.major_version, + [CLUSTER_FORM_FIELD_NAMES.ENVIRONMENT_ID]: environments.data?.data?.[0]?.id, + [CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME]: clusterName.data?.name ?? 'postgres-cluster', + })); + }; + void resetForm().then(() => setIsResetting(false)); + } + }, [deployments.data?.data, postgresVersions.data?.data, environments.data?.data, clusterName.data, methods]); + + const submitLocalCluster = async (values: ClusterFormValues) => { + if (values[CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_IS_SAVE_TO_CONSOLE] && !createSecretResultRef?.current) { + createSecretResultRef.current = await addSecretTrigger({ + requestSecretCreate: { + project_id: Number(currentProject), + type: values[CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD], + name: values[CLUSTER_FORM_FIELD_NAMES.SECRET_KEY_NAME], + value: getSecretBodyFromValues({ + ...values, + [SECRET_MODAL_CONTENT_FORM_FIELD_NAMES.SECRET_TYPE]: values[CLUSTER_FORM_FIELD_NAMES.AUTHENTICATION_METHOD], + }), + }, + }).unwrap(); + toast.success( + t('secretSuccessfullyCreated', { + ns: 'toasts', + secretName: values[CLUSTER_FORM_FIELD_NAMES.SECRET_KEY_NAME], + }), + ); + } + await addClusterTrigger({ + requestClusterCreate: mapFormValuesToRequestFields({ + values, + secretId: createSecretResultRef.current?.id ?? Number(values[CLUSTER_FORM_FIELD_NAMES.SECRET_ID]), + projectId: Number(currentProject), + }), + }).unwrap(); + toast.info( + t('clusterSuccessfullyCreated', { + ns: 'toasts', + clusterName: values[CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME], + }), + ); + }; + + const submitCloudCluster = async (values: ClusterFormValues) => { + await addClusterTrigger({ + requestClusterCreate: mapFormValuesToRequestFields({ + values, + secretId: secrets?.data?.data?.[0]?.id, + projectId: Number(currentProject), + }), + }).unwrap(); + toast.info( + t('clusterSuccessfullyCreated', { + ns: 'toasts', + clusterName: values[CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME], + }), + ); + }; + + const onSubmit = async (values: ClusterFormValues) => { + try { + values[CLUSTER_FORM_FIELD_NAMES.PROVIDER].code === PROVIDERS.LOCAL + ? await submitLocalCluster(values) + : await submitCloudCluster(values); + navigate(generateAbsoluteRouterPath(RouterPaths.clusters.absolutePath)); + } catch (e) { + handleRequestErrorCatch(e); + } + }; + + const cancelHandler = () => navigate(generateAbsoluteRouterPath(RouterPaths.clusters.absolutePath)); + + const { isValid, isSubmitting } = methods.formState; // spreading is required by React Hook Form to ensure correct form state + + return isResetting || deployments.isFetching || postgresVersions.isFetching || environments.isFetching ? ( + + ) : ( + + + +
+ + + {watchProvider?.code === PROVIDERS.LOCAL ? ( + + ) : ( + + )} + + + + + {watchProvider?.code !== PROVIDERS.LOCAL && secrets?.data?.data?.length !== 1 ? ( + + ) : ( + + )} + +
+ +
+
+
+ ); +}; + +export default ClusterForm; diff --git a/console/ui/src/widgets/cluster-overview-table/index.ts b/console/ui/src/widgets/cluster-overview-table/index.ts new file mode 100644 index 000000000..80eaaa422 --- /dev/null +++ b/console/ui/src/widgets/cluster-overview-table/index.ts @@ -0,0 +1,3 @@ +import ClusterOverviewTable from '@widgets/cluster-overview-table/ui'; + +export default ClusterOverviewTable; diff --git a/console/ui/src/widgets/cluster-overview-table/lib/hooks.tsx b/console/ui/src/widgets/cluster-overview-table/lib/hooks.tsx new file mode 100644 index 000000000..55557b8d8 --- /dev/null +++ b/console/ui/src/widgets/cluster-overview-table/lib/hooks.tsx @@ -0,0 +1,28 @@ +import { useMemo } from 'react'; +import { CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES } from '@widgets/cluster-overview-table/model/constants.ts'; +import { ClusterInfoInstance } from '@shared/api/api/clusters.ts'; +import { Box, Chip } from '@mui/material'; + +export const useGetOverviewClusterTableData = (data: ClusterInfoInstance[]) => { + return useMemo( + () => + data?.map((item) => ({ + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.NAME]: item?.name, + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.HOST]: item?.ip, + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.ROLE]: item?.role, + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.STATE]: item?.status, + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TIMELINE]: item?.timeline, + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.LAG_IN_MB]: item?.lag, + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TAGS]: item?.tags && ( + + {Object.entries(item.tags).map(([key, value]) => ( + + ))} + + ), + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.PENDING_RESTART]: String(item?.pending_restart), + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.ID]: item?.id, + })) ?? [], + [data], + ); +}; diff --git a/console/ui/src/widgets/cluster-overview-table/model/constants.ts b/console/ui/src/widgets/cluster-overview-table/model/constants.ts new file mode 100644 index 000000000..ae6fa0719 --- /dev/null +++ b/console/ui/src/widgets/cluster-overview-table/model/constants.ts @@ -0,0 +1,49 @@ +import { createMRTColumnHelper } from 'material-react-table'; +import { TFunction } from 'i18next'; +import { ClusterOverviewTableValues } from '@widgets/cluster-overview-table/model/types.ts'; + +export const CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES = Object.freeze({ + NAME: 'name', + HOST: 'host', + ROLE: 'role', + STATE: 'state', + TIMELINE: 'timeline', + LAG_IN_MB: 'lagInMb', + PENDING_RESTART: 'pendingRestart', + TAGS: 'tags', + ID: 'id', +}); + +const columnHelper = createMRTColumnHelper(); + +export const clusterOverviewTableColumns = (t: TFunction) => [ + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.NAME, { + header: t('name', { ns: 'shared' }), + }), + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.HOST, { + header: t('host', { ns: 'clusters' }), + size: 70, + }), + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.ROLE, { + header: t('role', { ns: 'clusters' }), + size: 120, + }), + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.STATE, { + header: t('state', { ns: 'clusters' }), + size: 110, + }), + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TIMELINE, { + header: t('timeline', { ns: 'clusters' }), + size: 80, + }), + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.LAG_IN_MB, { + header: t('lagInMb', { ns: 'clusters' }), + size: 140, + }), + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.PENDING_RESTART, { + header: t('pendingRestart', { ns: 'clusters' }), + }), + columnHelper.accessor(CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TAGS, { + header: t('tags', { ns: 'clusters' }), + }), +]; diff --git a/console/ui/src/widgets/cluster-overview-table/model/types.ts b/console/ui/src/widgets/cluster-overview-table/model/types.ts new file mode 100644 index 000000000..4e080da08 --- /dev/null +++ b/console/ui/src/widgets/cluster-overview-table/model/types.ts @@ -0,0 +1,19 @@ +import { CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES } from '@widgets/cluster-overview-table/model/constants.ts'; +import { ClusterInfoInstance } from '@shared/api/api/clusters.ts'; + +export interface ClusterOverviewTableProps { + clusterName?: string; + items?: ClusterInfoInstance[]; + isLoading?: boolean; +} + +export interface ClusterOverviewTableValues { + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.NAME]: string; + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.HOST]: string; + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.ROLE]: string; + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.STATE]: string; + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TIMELINE]: number; + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.LAG_IN_MB]: number; + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.PENDING_RESTART]: string; + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TAGS]: string; +} diff --git a/console/ui/src/widgets/cluster-overview-table/ui/ClustersOverviewTableButtons.tsx b/console/ui/src/widgets/cluster-overview-table/ui/ClustersOverviewTableButtons.tsx new file mode 100644 index 000000000..ea404c556 --- /dev/null +++ b/console/ui/src/widgets/cluster-overview-table/ui/ClustersOverviewTableButtons.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react'; +import { Button, Stack } from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import { useTranslation } from 'react-i18next'; +import { usePostClustersByIdRefreshMutation } from '@shared/api/api/clusters.ts'; +import { useParams } from 'react-router-dom'; + +const ClustersOverviewTableButtons: FC = () => { + const { t } = useTranslation('shared'); + const { clusterId } = useParams(); + + const [refreshClusterTrigger] = usePostClustersByIdRefreshMutation(); + + const handleRefresh = async () => { + await refreshClusterTrigger({ id: Number(clusterId) }); + }; + + return ( + + + + ); +}; + +export default ClustersOverviewTableButtons; diff --git a/console/ui/src/widgets/cluster-overview-table/ui/index.tsx b/console/ui/src/widgets/cluster-overview-table/ui/index.tsx new file mode 100644 index 000000000..1af8b8489 --- /dev/null +++ b/console/ui/src/widgets/cluster-overview-table/ui/index.tsx @@ -0,0 +1,56 @@ +import { FC, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { MRT_ColumnDef, MRT_RowData, MRT_TableOptions } from 'material-react-table'; +import { ClusterOverviewTableProps } from '@widgets/cluster-overview-table/model/types.ts'; +import { + CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES, + clusterOverviewTableColumns, +} from '@widgets/cluster-overview-table/model/constants.ts'; +import ClustersOverviewTableButtons from '@widgets/cluster-overview-table/ui/ClustersOverviewTableButtons.tsx'; +import { useGetOverviewClusterTableData } from '@widgets/cluster-overview-table/lib/hooks.tsx'; +import { ClusterInfo } from '@shared/api/api/clusters.ts'; +import DefaultTable from '@shared/ui/default-table'; +import { Stack, Typography } from '@mui/material'; +import ClustersOverviewTableRowActions from '@features/clusters-overview-table-row-actions'; + +const ClusterOverviewTable: FC = ({ clusterName = '', items, isLoading }) => { + const { t, i18n } = useTranslation('clusters'); + + const columns = useMemo[]>(() => clusterOverviewTableColumns(t), [i18n.language]); + + const data = useGetOverviewClusterTableData(items); + + const tableConfig: MRT_TableOptions = { + columns, + data, + enablePagination: false, + enableRowActions: true, + showGlobalFilter: false, + state: { + isLoading, + }, + initialState: { + columnVisibility: { + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.PENDING_RESTART]: false, + [CLUSTER_OVERVIEW_TABLE_COLUMN_NAMES.TAGS]: false, + }, + }, + renderRowActionMenuItems: ({ row, closeMenu }) => ( + + ), + }; + + return ( + <> + + + {t('cluster')}: {clusterName} + + + + + + ); +}; + +export default ClusterOverviewTable; diff --git a/console/ui/src/widgets/cluster-summary/assets/awsIcon.svg b/console/ui/src/widgets/cluster-summary/assets/awsIcon.svg new file mode 100644 index 000000000..3dec8e73f --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/assets/awsIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/widgets/cluster-summary/assets/azureIcon.svg b/console/ui/src/widgets/cluster-summary/assets/azureIcon.svg new file mode 100644 index 000000000..e24e2189c --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/assets/azureIcon.svg @@ -0,0 +1,27 @@ + + + Azure + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/ui/src/widgets/cluster-summary/assets/digitaloceanIcon.svg b/console/ui/src/widgets/cluster-summary/assets/digitaloceanIcon.svg new file mode 100644 index 000000000..5a81f2481 --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/assets/digitaloceanIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/widgets/cluster-summary/assets/gcpIcon.svg b/console/ui/src/widgets/cluster-summary/assets/gcpIcon.svg new file mode 100644 index 000000000..92da9d7cc --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/assets/gcpIcon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/console/ui/src/widgets/cluster-summary/assets/hetznerIcon.svg b/console/ui/src/widgets/cluster-summary/assets/hetznerIcon.svg new file mode 100644 index 000000000..73b15fff7 --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/assets/hetznerIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/widgets/cluster-summary/assets/hetznerIcon2.svg b/console/ui/src/widgets/cluster-summary/assets/hetznerIcon2.svg new file mode 100644 index 000000000..c8c8ad682 --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/assets/hetznerIcon2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/widgets/cluster-summary/index.ts b/console/ui/src/widgets/cluster-summary/index.ts new file mode 100644 index 000000000..1c5f9406f --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/index.ts @@ -0,0 +1,3 @@ +import ClusterSummary from '@widgets/cluster-summary/ui'; + +export default ClusterSummary; diff --git a/console/ui/src/widgets/cluster-summary/lib/hooks.tsx b/console/ui/src/widgets/cluster-summary/lib/hooks.tsx new file mode 100644 index 000000000..9ecacc2c8 --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/lib/hooks.tsx @@ -0,0 +1,205 @@ +import { Icon, Link, Stack, Typography } from '@mui/material'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { Trans, useTranslation } from 'react-i18next'; +import { providerNamePricingListMap } from '@widgets/cluster-summary/model/constants.ts'; +import RamIcon from '@shared/assets/ramIcon.svg?react'; +import InstanceIcon from '@shared/assets/instanceIcon.svg?react'; +import StorageIcon from '@shared/assets/storageIcon.svg?react'; +import LanIcon from '@shared/assets/lanIcon.svg?react'; +import FlagIcon from '@shared/assets/flagIcon.svg?react'; +import CheckIcon from '@shared/assets/checkIcon.svg?react'; +import CpuIcon from '@shared/assets/cpuIcon.svg?react'; +import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined'; +import { + CloudProviderClustersSummary, + LocalClustersSummary, + UseGetSummaryConfigProps, +} from '@widgets/cluster-summary/model/types.ts'; + +const useGetCloudProviderConfig = () => { + const { t } = useTranslation(['clusters', 'shared']); + + return (data: CloudProviderClustersSummary) => { + const defaultVolume = data[CLUSTER_FORM_FIELD_NAMES.PROVIDER]?.volumes?.find((volume) => volume?.is_default) ?? {}; + + return [ + { + title: t('name'), + children: {data[CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME]}, + }, + { + title: t('postgresVersion'), + children: {data[CLUSTER_FORM_FIELD_NAMES.POSTGRES_VERSION]}, + }, + { + title: t('cloud'), + children: ( + + + {data[CLUSTER_FORM_FIELD_NAMES.PROVIDER]?.description?.[0]} + + {data[CLUSTER_FORM_FIELD_NAMES.PROVIDER]?.description} + + ), + }, + { + title: t('region'), + children: ( + + {data[CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG]?.code} + + + {data[CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG]?.location} + + + ), + }, + { + title: t('instanceType'), + children: ( + + {data[CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]?.code} + + + + {data[CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]?.cpu} CPU + + + + {data[CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]?.ram} RAM + + + + ), + }, + { + title: t('numberOfInstances'), + children: ( + + + {data[CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT]} + + ), + }, + { + title: t('dataDiskStorage'), + children: ( + + + {data[CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT]} GB + + ), + }, + { + title: `${t('estimatedMonthlyPrice')}*`, + children: ( + <> + + ~ + {`${data[CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]?.currency}${( + data[CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]?.price_monthly * + data[CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT] + + defaultVolume?.price_monthly * + data[CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT] * + data[CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT] + )?.toFixed(2)}/${t('month', { ns: 'shared' })}`} + + + + ~ + {`${data[CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]?.currency}${data[ + CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG + ]?.price_monthly.toFixed(2)}/${t('perServer', { ns: 'clusters' })}`} + , ~ + {`${defaultVolume?.currency}${( + defaultVolume?.price_monthly * data[CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT] + )?.toFixed(2)}/${t('perDisk', { ns: 'clusters' })}`} + + + + + + + + + ), + }, + ]; + }; +}; + +const useGetLocalMachineConfig = () => { + const { t } = useTranslation(['clusters', 'shared']); + + return (data: LocalClustersSummary) => [ + { + title: t('name'), + children: {data[CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME]}, + }, + { + title: t('postgresVersion'), + children: {data[CLUSTER_FORM_FIELD_NAMES.POSTGRES_VERSION]}, + }, + { + title: t('numberOfServers'), + children: ( + + + {data[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]?.length} + + ), + }, + { + title: t('loadBalancing'), + children: ( + + + + {data[CLUSTER_FORM_FIELD_NAMES.IS_HAPROXY_LOAD_BALANCER] + ? t('on', { ns: 'shared' }) + : t('off', { ns: 'shared' })} + + + ), + }, + { + title: t('highAvailability'), + children: ( + + + {data[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]?.length >= 3 ? ( + + ) : ( + + )} + + {data[CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]?.length >= 3 + ? t('on', { ns: 'shared' }) + : t('off', { ns: 'shared' })} + + + + {t('highAvailabilityInfo')} + + + ), + }, + ]; +}; + +export const useGetSummaryConfig = ({ isCloudProvider, data }: UseGetSummaryConfigProps) => { + const cloudProviderConfig = useGetCloudProviderConfig(); + const localProviderConfig = useGetLocalMachineConfig(); + + return isCloudProvider + ? cloudProviderConfig(data as CloudProviderClustersSummary) + : localProviderConfig(data as LocalClustersSummary); +}; diff --git a/console/ui/src/widgets/cluster-summary/model/constants.ts b/console/ui/src/widgets/cluster-summary/model/constants.ts new file mode 100644 index 000000000..e69114190 --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/model/constants.ts @@ -0,0 +1,22 @@ +import { PROVIDERS } from '@shared/config/constants.ts'; +import AWSIcon from '@widgets/cluster-summary/assets/awsIcon.svg'; +import GCPIcon from '@widgets/cluster-summary/assets/gcpIcon.svg'; +import AzureIcon from '@widgets/cluster-summary/assets/azureIcon.svg'; +import DigitalOceanIcon from '@widgets/cluster-summary/assets/digitaloceanIcon.svg'; +import HetznerIcon from '@widgets/cluster-summary/assets/hetznerIcon2.svg'; + +export const providerNamePricingListMap = Object.freeze({ + [PROVIDERS.AWS]: 'https://aws.amazon.com/ec2/pricing/on-demand/', + [PROVIDERS.GCP]: 'https://cloud.google.com/compute/vm-instance-pricing/#general-purpose_machine_type_family', + [PROVIDERS.AZURE]: 'https://azure.microsoft.com/en-us/pricing/details/virtual-machines/linux/', + [PROVIDERS.DIGITAL_OCEAN]: 'https://www.digitalocean.com/pricing/droplets/', + [PROVIDERS.HETZNER]: 'https://www.hetzner.com/cloud/', +}); + +export const clusterSummaryNameIconProvidersMap = Object.freeze({ + [PROVIDERS.AWS]: AWSIcon, + [PROVIDERS.GCP]: GCPIcon, + [PROVIDERS.AZURE]: AzureIcon, + [PROVIDERS.DIGITAL_OCEAN]: DigitalOceanIcon, + [PROVIDERS.HETZNER]: HetznerIcon, +}); diff --git a/console/ui/src/widgets/cluster-summary/model/types.ts b/console/ui/src/widgets/cluster-summary/model/types.ts new file mode 100644 index 000000000..dfb8a898f --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/model/types.ts @@ -0,0 +1,37 @@ +import { ReactElement } from 'react'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; + +export interface SharedClusterSummaryProps { + [CLUSTER_FORM_FIELD_NAMES.CLUSTER_NAME]: string; + [CLUSTER_FORM_FIELD_NAMES.POSTGRES_VERSION]: number; +} + +export interface CloudProviderClustersSummary extends SharedClusterSummaryProps { + [CLUSTER_FORM_FIELD_NAMES.PROVIDER]: { + icon: ReactElement; + name: string; + }; + [CLUSTER_FORM_FIELD_NAMES.REGION_CONFIG]: { + name: string; + place: string; + }; + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]: { + name: string; + cpu: number; + ram: number; + }; + [CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT]: number; + [CLUSTER_FORM_FIELD_NAMES.STORAGE_AMOUNT]: number; + [CLUSTER_FORM_FIELD_NAMES.INSTANCE_CONFIG]: number; + [CLUSTER_FORM_FIELD_NAMES.INSTANCES_AMOUNT]: number; +} + +export interface LocalClustersSummary extends SharedClusterSummaryProps { + [CLUSTER_FORM_FIELD_NAMES.DATABASE_SERVERS]: number; + [CLUSTER_FORM_FIELD_NAMES.IS_HAPROXY_LOAD_BALANCER]: boolean; +} + +export interface UseGetSummaryConfigProps { + isCloudProvider: boolean; + data: CloudProviderClustersSummary | LocalClustersSummary; +} diff --git a/console/ui/src/widgets/cluster-summary/ui/index.tsx b/console/ui/src/widgets/cluster-summary/ui/index.tsx new file mode 100644 index 000000000..06d83508b --- /dev/null +++ b/console/ui/src/widgets/cluster-summary/ui/index.tsx @@ -0,0 +1,50 @@ +import { FC } from 'react'; +import { Divider, Paper, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { CLUSTER_FORM_FIELD_NAMES } from '@widgets/cluster-form/model/constants.ts'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { useGetSummaryConfig } from '@widgets/cluster-summary/lib/hooks.tsx'; +import { PROVIDERS } from '@shared/config/constants.ts'; +import InfoCardBody from '@shared/ui/info-card-body'; +import { clusterSummaryNameIconProvidersMap } from '@widgets/cluster-summary/model/constants.ts'; + +const ClusterSummary: FC = () => { + const { t } = useTranslation(['clusters', 'shared']); + const { control } = useFormContext(); + + const watchValues = useWatch({ + control, + }); + + const config = useGetSummaryConfig({ + isCloudProvider: watchValues[CLUSTER_FORM_FIELD_NAMES.PROVIDER]?.code !== PROVIDERS.LOCAL, + data: { + ...watchValues, + [CLUSTER_FORM_FIELD_NAMES.PROVIDER]: { + ...watchValues[CLUSTER_FORM_FIELD_NAMES.PROVIDER], + icon: clusterSummaryNameIconProvidersMap[watchValues[CLUSTER_FORM_FIELD_NAMES.PROVIDER]?.code], + }, + }, + }); + + return ( + + + {t('summary')} + + + + + ); +}; + +export default ClusterSummary; diff --git a/console/ui/src/widgets/clusters-table/assets/correctIcon.svg b/console/ui/src/widgets/clusters-table/assets/correctIcon.svg new file mode 100644 index 000000000..17486953f --- /dev/null +++ b/console/ui/src/widgets/clusters-table/assets/correctIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/widgets/clusters-table/assets/errorIcon.svg b/console/ui/src/widgets/clusters-table/assets/errorIcon.svg new file mode 100644 index 000000000..74c4d4d62 --- /dev/null +++ b/console/ui/src/widgets/clusters-table/assets/errorIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/widgets/clusters-table/assets/noClustersIcon.svg b/console/ui/src/widgets/clusters-table/assets/noClustersIcon.svg new file mode 100644 index 000000000..9eb95a45e --- /dev/null +++ b/console/ui/src/widgets/clusters-table/assets/noClustersIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/console/ui/src/widgets/clusters-table/assets/warningIcon.svg b/console/ui/src/widgets/clusters-table/assets/warningIcon.svg new file mode 100644 index 000000000..19c88a48c --- /dev/null +++ b/console/ui/src/widgets/clusters-table/assets/warningIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/console/ui/src/widgets/clusters-table/index.ts b/console/ui/src/widgets/clusters-table/index.ts new file mode 100644 index 000000000..bd1d451f8 --- /dev/null +++ b/console/ui/src/widgets/clusters-table/index.ts @@ -0,0 +1,3 @@ +import ClustersTable from '@widgets/clusters-table/ui'; + +export default ClustersTable; diff --git a/console/ui/src/widgets/clusters-table/lib/functions.ts b/console/ui/src/widgets/clusters-table/lib/functions.ts new file mode 100644 index 000000000..07e2db023 --- /dev/null +++ b/console/ui/src/widgets/clusters-table/lib/functions.ts @@ -0,0 +1,82 @@ +import Index from '@app/router/routerPathsConfig'; +import { convertTimestampToReadableTime, generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; +import { NavigateFunction } from 'react-router-dom'; +import { createMRTColumnHelper } from 'material-react-table'; +import { ClustersTableValues } from '@widgets/clusters-table/model/types.ts'; +import { RankingInfo, rankings, rankItem } from '@tanstack/match-sorter-utils'; +import { CLUSTER_STATUSES, CLUSTER_TABLE_COLUMN_NAMES } from '@widgets/clusters-table/model/constants.ts'; + +export const createClusterButtonHandler = (navigate: NavigateFunction) => () => + navigate(generateAbsoluteRouterPath(Index.clusters.add.absolutePath)); + +const columnHelper = createMRTColumnHelper(); + +export const getClusterTableColumns = ({ t, environmentOptions, postgresVersionOptions }) => [ + // note: changing table cell items content might need new custom filter function + columnHelper.accessor(CLUSTER_TABLE_COLUMN_NAMES.NAME, { + header: t('clusterName', { ns: 'clusters' }), + filterFn: (row: Row, id: string, filterValue: string | number, addMeta: (item: RankingInfo) => void) => { + // custom filter callback because of ReactNode as values + const itemRank = rankItem(row.getValue(CLUSTER_TABLE_COLUMN_NAMES.NAME).props.children, filterValue as string, { + threshold: rankings.MATCHES, + }); + addMeta(itemRank); + return itemRank.passed; + }, + size: 150, + enableHiding: false, + grow: true, + visibleInShowHideMenu: false, + }), + columnHelper.accessor(CLUSTER_TABLE_COLUMN_NAMES.STATUS, { + header: t('status', { ns: 'shared' }), + size: 120, + filterVariant: 'select', + filterSelectOptions: Object.values(CLUSTER_STATUSES), + filterFn: (row: Row, id: string, filterValue: string | number, addMeta: (item: RankingInfo) => void) => { + const itemRank = rankItem( + row.getValue(CLUSTER_TABLE_COLUMN_NAMES.STATUS).props.children[1].props.children, // custom filter callback because of ReactNode as values + filterValue as string, + { + threshold: rankings.MATCHES, + }, + ); + addMeta(itemRank); + return itemRank.passed; + }, + grow: true, + }), + columnHelper.accessor(CLUSTER_TABLE_COLUMN_NAMES.CREATION_TIME, { + accessorFn: (originalRow) => new Date(originalRow[CLUSTER_TABLE_COLUMN_NAMES.CREATION_TIME]), //convert to date for sorting and filtering + header: t('creationTime', { ns: 'clusters' }), + size: 260, + filterVariant: 'date-range', + grow: true, + muiFilterTextFieldProps: { sx: { display: 'flex', flexDirection: 'column' } }, + muiFilterDatePickerProps: { sx: { display: 'flex', flexDirection: 'column' }, size: 'small' }, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), // convert back to string to display + }), + columnHelper.accessor(CLUSTER_TABLE_COLUMN_NAMES.ENVIRONMENT, { + header: t('environment', { ns: 'shared' }), + size: 140, + filterVariant: 'select', + filterSelectOptions: environmentOptions, + grow: true, + }), + columnHelper.accessor(CLUSTER_TABLE_COLUMN_NAMES.SERVERS, { + header: t('servers', { ns: 'clusters' }), + size: 120, + grow: true, + }), + columnHelper.accessor(CLUSTER_TABLE_COLUMN_NAMES.POSTGRES_VERSION, { + header: t('postgresVersion', { ns: 'clusters' }), + size: 150, + filterVariant: 'select', + filterSelectOptions: postgresVersionOptions, + grow: true, + }), + columnHelper.accessor(CLUSTER_TABLE_COLUMN_NAMES.LOCATION, { + header: t('location', { ns: 'clusters' }), + grow: true, + }), +]; diff --git a/console/ui/src/widgets/clusters-table/lib/hooks.tsx b/console/ui/src/widgets/clusters-table/lib/hooks.tsx new file mode 100644 index 000000000..fe389f060 --- /dev/null +++ b/console/ui/src/widgets/clusters-table/lib/hooks.tsx @@ -0,0 +1,48 @@ +import { useMemo } from 'react'; +import { + CLUSTER_STATUSES, + CLUSTER_TABLE_COLUMN_NAMES, + clusterStatusColorNamesMap, +} from '@widgets/clusters-table/model/constants.ts'; +import { CircularProgress, Link, Stack, Typography } from '@mui/material'; +import { generateAbsoluteRouterPath } from '@shared/lib/functions.ts'; +import RouterPaths from '@app/router/routerPathsConfig'; +import { ClusterInfo } from '@shared/api/api/clusters.ts'; + +export const useGetClustersTableData = (data: ClusterInfo[]) => + useMemo( + () => + data?.map((cluster) => ({ + [CLUSTER_TABLE_COLUMN_NAMES.NAME]: [CLUSTER_STATUSES.DEPLOYING, CLUSTER_STATUSES.FAILED].some( + (status) => status === cluster.status, + ) ? ( + {cluster.name} + ) : ( + + {cluster.name} + + ), + [CLUSTER_TABLE_COLUMN_NAMES.STATUS]: ( + + {cluster.status === CLUSTER_STATUSES.DEPLOYING ? ( + + ) : clusterStatusColorNamesMap[cluster.status] ? ( + {cluster.status} + ) : null} + {cluster.status} + + ), + [CLUSTER_TABLE_COLUMN_NAMES.CREATION_TIME]: cluster.creation_time, + [CLUSTER_TABLE_COLUMN_NAMES.ENVIRONMENT]: cluster.environment, + [CLUSTER_TABLE_COLUMN_NAMES.SERVERS]: cluster.servers?.length ?? '-', + [CLUSTER_TABLE_COLUMN_NAMES.POSTGRES_VERSION]: cluster?.postgres_version ?? '-', + [CLUSTER_TABLE_COLUMN_NAMES.LOCATION]: cluster?.cluster_location ?? '-', + [CLUSTER_TABLE_COLUMN_NAMES.ID]: cluster.id, // not displayed, required only for correct cluster removal + })) ?? [], + [data], + ); diff --git a/console/ui/src/widgets/clusters-table/model/constants.ts b/console/ui/src/widgets/clusters-table/model/constants.ts new file mode 100644 index 000000000..d741dc5a5 --- /dev/null +++ b/console/ui/src/widgets/clusters-table/model/constants.ts @@ -0,0 +1,33 @@ +import CorrectIcon from '../assets/correctIcon.svg'; +import WarningIcon from '../assets/warningIcon.svg'; +import ErrorIcon from '../assets/errorIcon.svg'; + +export const CLUSTER_TABLE_COLUMN_NAMES = Object.freeze({ + // names are used as sorting params, changes will break sorting + NAME: 'name', + STATUS: 'status', + CREATION_TIME: 'created_at', + ENVIRONMENT: 'environment', + SERVERS: 'server_count', + POSTGRES_VERSION: 'postgres_version', + LOCATION: 'location', + ACTIONS: 'actions', + ID: 'id', +}); + +export const CLUSTER_STATUSES = Object.freeze({ + DEPLOYING: 'deploying', + READY: 'ready', + FAILED: 'failed', + HEALTHY: 'healthy', + UNHEALTHY: 'unhealthy', + DEGRADED: 'degraded', + UNAVAILABLE: 'unavailable', +}); + +export const clusterStatusColorNamesMap = Object.freeze({ + [CLUSTER_STATUSES.HEALTHY]: CorrectIcon, + [CLUSTER_STATUSES.UNHEALTHY]: WarningIcon, + [CLUSTER_STATUSES.DEGRADED]: ErrorIcon, + [CLUSTER_STATUSES.UNAVAILABLE]: ErrorIcon, +}); diff --git a/console/ui/src/widgets/clusters-table/model/types.ts b/console/ui/src/widgets/clusters-table/model/types.ts new file mode 100644 index 000000000..0c95caffd --- /dev/null +++ b/console/ui/src/widgets/clusters-table/model/types.ts @@ -0,0 +1,11 @@ +import { CLUSTER_TABLE_COLUMN_NAMES } from '@widgets/clusters-table/model/constants.ts'; + +export interface ClustersTableValues { + [CLUSTER_TABLE_COLUMN_NAMES.NAME]: string; + [CLUSTER_TABLE_COLUMN_NAMES.STATUS]: Element; + [CLUSTER_TABLE_COLUMN_NAMES.CREATION_TIME]: string; + [CLUSTER_TABLE_COLUMN_NAMES.ENVIRONMENT]: string; + [CLUSTER_TABLE_COLUMN_NAMES.SERVERS]: number; + [CLUSTER_TABLE_COLUMN_NAMES.POSTGRES_VERSION]: number; + [CLUSTER_TABLE_COLUMN_NAMES.LOCATION]: string; +} diff --git a/console/ui/src/widgets/clusters-table/ui/ClustersEmptyRowsFallback.tsx b/console/ui/src/widgets/clusters-table/ui/ClustersEmptyRowsFallback.tsx new file mode 100644 index 000000000..23b0b668c --- /dev/null +++ b/console/ui/src/widgets/clusters-table/ui/ClustersEmptyRowsFallback.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { Box, Button, Stack, Typography } from '@mui/material'; +import { createClusterButtonHandler } from '@widgets/clusters-table/lib/functions.ts'; +import DatabaseIcon from '@assets/databaseIcon.svg?react'; + +const ClustersEmptyRowsFallback: React.FC = () => { + const { t } = useTranslation('clusters'); + const navigate = useNavigate(); + + return ( + + + + + + {t('noPostgresClustersTitle')} + + + {t('noPostgresClustersLine1', { createCluster: t('createCluster') })} + + + + + + ); +}; + +export default ClustersEmptyRowsFallback; diff --git a/console/ui/src/widgets/clusters-table/ui/index.tsx b/console/ui/src/widgets/clusters-table/ui/index.tsx new file mode 100644 index 000000000..292de4c6b --- /dev/null +++ b/console/ui/src/widgets/clusters-table/ui/index.tsx @@ -0,0 +1,99 @@ +import { FC, useMemo, useState } from 'react'; +import { CLUSTER_TABLE_COLUMN_NAMES } from '@widgets/clusters-table/model/constants.ts'; +import { useTranslation } from 'react-i18next'; +import { ClustersTableValues } from '@widgets/clusters-table/model/types.ts'; +import { MRT_ColumnDef, MRT_RowData, MRT_TableOptions } from 'material-react-table'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import { CLUSTERS_POLLING_INTERVAL, PAGINATION_LIMIT_OPTIONS } from '@shared/config/constants.ts'; +import ClustersTableButtons from '@features/clusters-table-buttons'; +import { useGetClustersQuery } from '@shared/api/api/clusters.ts'; +import { useGetClustersTableData } from '@widgets/clusters-table/lib/hooks.tsx'; +import { useGetEnvironmentsQuery } from '@shared/api/api/environments.ts'; +import { useGetPostgresVersionsQuery } from '@shared/api/api/other.ts'; +import ClustersTableRowActions from '@features/clusters-table-row-actions'; +import ClustersEmptyRowsFallback from '@widgets/clusters-table/ui/ClustersEmptyRowsFallback.tsx'; +import { getClusterTableColumns } from '@widgets/clusters-table/lib/functions.ts'; +import { manageSortingOrder } from '@shared/lib/functions.ts'; +import { useQueryPolling } from '@shared/lib/hooks.tsx'; +import DefaultTable from '@shared/ui/default-table'; + +const ClustersTable: FC = () => { + const { t, i18n } = useTranslation('clusters'); + + const currentProject = useAppSelector(selectCurrentProject); + + const [sorting, setSorting] = useState([ + { + id: CLUSTER_TABLE_COLUMN_NAMES.CREATION_TIME, + desc: true, + }, + ]); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGINATION_LIMIT_OPTIONS[1].value, + }); + + const environments = useGetEnvironmentsQuery({ offset: 0, limit: 999_999_999 }); + const postgresVersions = useGetPostgresVersionsQuery(); + + const clustersList = useQueryPolling( + () => + useGetClustersQuery({ + projectId: Number(currentProject), // TODO: projectId, projectCode + offset: pagination.pageIndex * pagination.pageSize, + limit: pagination.pageSize, + ...(sorting?.[0] ? { sortBy: manageSortingOrder(sorting[0]) } : {}), + }), + CLUSTERS_POLLING_INTERVAL, + ); + + const columns = useMemo[]>( + () => + getClusterTableColumns({ + t, + environmentOptions: environments.data?.data?.map((environment) => environment.name) ?? [], + postgresVersionOptions: postgresVersions.data?.data?.map((version) => version.major_version) ?? [], + }), + [i18n.language, environments.data?.data, postgresVersions.data?.data], + ); + + const data = useGetClustersTableData(clustersList.data?.data); + + const tableConfig: MRT_TableOptions = { + columns, + data, + enablePagination: true, + enableRowSelection: true, + showGlobalFilter: true, + manualPagination: true, + enableRowActions: true, + enableStickyHeader: true, + enableMultiSort: false, + onPaginationChange: setPagination, + onSortingChange: setSorting, + rowCount: clustersList.data?.meta?.count ?? 0, + state: { + isLoading: clustersList.isFetching || environments.isFetching || postgresVersions.isFetching, + pagination, + sorting, + }, + initialState: { + columnVisibility: { + [CLUSTER_TABLE_COLUMN_NAMES.LOCATION]: false, + }, + }, + renderRowActionMenuItems: ({ closeMenu, row }) => , + renderEmptyRowsFallback: () => , + }; + + return ( + <> + + + + ); +}; + +export default ClustersTable; diff --git a/console/ui/src/widgets/environments-table/index.ts b/console/ui/src/widgets/environments-table/index.ts new file mode 100644 index 000000000..32d4c9056 --- /dev/null +++ b/console/ui/src/widgets/environments-table/index.ts @@ -0,0 +1,3 @@ +import EnvironmentsTable from '@widgets/environments-table/ui'; + +export default EnvironmentsTable; diff --git a/console/ui/src/widgets/environments-table/lib/hooks.tsx b/console/ui/src/widgets/environments-table/lib/hooks.tsx new file mode 100644 index 000000000..9f7293f36 --- /dev/null +++ b/console/ui/src/widgets/environments-table/lib/hooks.tsx @@ -0,0 +1,16 @@ +import { useMemo } from 'react'; +import { ENVIRONMENTS_TABLE_COLUMN_NAMES } from '@widgets/environments-table/model/constants.ts'; +import { ResponseEnvironment } from '@shared/api/api/environments.ts'; + +export const useGetEnvironmentsTableData = (data: ResponseEnvironment[]) => + useMemo( + () => + data?.map((secret) => ({ + [ENVIRONMENTS_TABLE_COLUMN_NAMES.ID]: secret.id, + [ENVIRONMENTS_TABLE_COLUMN_NAMES.NAME]: secret.name, + [ENVIRONMENTS_TABLE_COLUMN_NAMES.CREATED]: secret.created_at, + [ENVIRONMENTS_TABLE_COLUMN_NAMES.UPDATED]: secret.updated_at, + [ENVIRONMENTS_TABLE_COLUMN_NAMES.DESCRIPTION]: secret.description ?? '-', + })) ?? [], + [data], + ); diff --git a/console/ui/src/widgets/environments-table/model/constants.ts b/console/ui/src/widgets/environments-table/model/constants.ts new file mode 100644 index 000000000..f63231108 --- /dev/null +++ b/console/ui/src/widgets/environments-table/model/constants.ts @@ -0,0 +1,44 @@ +import { createMRTColumnHelper } from 'material-react-table'; +import { TFunction } from 'i18next'; +import { convertTimestampToReadableTime } from '@shared/lib/functions.ts'; +import { EnvironmentTableValues } from '@widgets/environments-table/model/types.ts'; + +export const ENVIRONMENTS_TABLE_COLUMN_NAMES = Object.freeze({ + ID: 'id', + NAME: 'name', + DESCRIPTION: 'description', + CREATED: 'created_at', + UPDATED: 'updated_at', +}); + +const columnHelper = createMRTColumnHelper(); + +export const environmentTableColumns = (t: TFunction) => [ + columnHelper.accessor(ENVIRONMENTS_TABLE_COLUMN_NAMES.ID, { + header: t('id', { ns: 'shared' }), + size: 80, + grow: true, + }), + columnHelper.accessor(ENVIRONMENTS_TABLE_COLUMN_NAMES.NAME, { + header: t('name', { ns: 'shared' }), + size: 80, + grow: true, + }), + columnHelper.accessor(ENVIRONMENTS_TABLE_COLUMN_NAMES.CREATED, { + header: t('created', { ns: 'shared' }), + size: 150, + grow: true, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), // convert back to string for display + }), + columnHelper.accessor(ENVIRONMENTS_TABLE_COLUMN_NAMES.UPDATED, { + header: t('updated', { ns: 'shared' }), + size: 150, + grow: true, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), // convert back to string for display + }), + columnHelper.accessor(ENVIRONMENTS_TABLE_COLUMN_NAMES.DESCRIPTION, { + header: t('description', { ns: 'shared' }), + size: 150, + grow: true, + }), +]; diff --git a/console/ui/src/widgets/environments-table/model/types.ts b/console/ui/src/widgets/environments-table/model/types.ts new file mode 100644 index 000000000..3c1719038 --- /dev/null +++ b/console/ui/src/widgets/environments-table/model/types.ts @@ -0,0 +1,9 @@ +import { PROJECTS_TABLE_COLUMN_NAMES } from '@widgets/projects-table/model/constants.ts'; + +export interface EnvironmentTableValues { + [PROJECTS_TABLE_COLUMN_NAMES.ID]: string; + [PROJECTS_TABLE_COLUMN_NAMES.NAME]: string; + [PROJECTS_TABLE_COLUMN_NAMES.DESCRIPTION]: 'description'; + [PROJECTS_TABLE_COLUMN_NAMES.CREATED]: 'created_at'; + [PROJECTS_TABLE_COLUMN_NAMES.UPDATED]: 'updated_at'; +} diff --git a/console/ui/src/widgets/environments-table/ui/EnvironmentsTableButtons.tsx b/console/ui/src/widgets/environments-table/ui/EnvironmentsTableButtons.tsx new file mode 100644 index 000000000..71a41b78e --- /dev/null +++ b/console/ui/src/widgets/environments-table/ui/EnvironmentsTableButtons.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { Stack } from '@mui/material'; +import AddEnvironment from '@features/add-environment'; + +const EnvironmentsTableButtons: FC = () => { + return ( + + + + ); +}; + +export default EnvironmentsTableButtons; diff --git a/console/ui/src/widgets/environments-table/ui/index.tsx b/console/ui/src/widgets/environments-table/ui/index.tsx new file mode 100644 index 000000000..9484f049b --- /dev/null +++ b/console/ui/src/widgets/environments-table/ui/index.tsx @@ -0,0 +1,58 @@ +import React, { FC, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PAGINATION_LIMIT_OPTIONS } from '@shared/config/constants.ts'; +import { MRT_ColumnDef, MRT_RowData, MRT_TableOptions } from 'material-react-table'; +import { EnvironmentTableValues } from '@widgets/environments-table/model/types.ts'; +import { environmentTableColumns } from '@widgets/environments-table/model/constants.ts'; +import { useGetEnvironmentsTableData } from '@widgets/environments-table/lib/hooks.tsx'; +import { useGetEnvironmentsQuery } from '@shared/api/api/environments.ts'; +import EnvironmentsTableButtons from '@widgets/environments-table/ui/EnvironmentsTableButtons.tsx'; +import EnvironmentsTableRowActions from '@features/environments-table-row-actions/ui'; +import DefaultTable from '@shared/ui/default-table'; + +const EnvironmentsTable: FC = () => { + const { t, i18n } = useTranslation(['settings', 'shared']); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGINATION_LIMIT_OPTIONS[1].value, + }); + + const environmentsList = useGetEnvironmentsQuery({ + offset: pagination.pageIndex * pagination.pageSize, + limit: pagination.pageSize, + }); + + const columns = useMemo[]>(() => environmentTableColumns(t), [i18n.language]); + + const data = useGetEnvironmentsTableData(environmentsList.data?.data); + + const tableConfig: MRT_TableOptions = { + columns, + data, + enablePagination: true, + enableRowSelection: true, + showGlobalFilter: true, + enableRowActions: true, + enableStickyHeader: true, + enableMultiSort: false, + enableSorting: false, + onPaginationChange: setPagination, + manualPagination: true, + rowCount: environmentsList.data?.meta?.count ?? 0, + state: { + isLoading: environmentsList.isFetching, + pagination, + }, + renderRowActionMenuItems: ({ closeMenu, row }) => , + }; + + return ( + <> + + + + ); +}; + +export default EnvironmentsTable; diff --git a/console/ui/src/widgets/header/index.ts b/console/ui/src/widgets/header/index.ts new file mode 100644 index 000000000..eab9de354 --- /dev/null +++ b/console/ui/src/widgets/header/index.ts @@ -0,0 +1,3 @@ +import Header from '@widgets/header/ui'; + +export default Header; diff --git a/console/ui/src/widgets/header/ui/index.tsx b/console/ui/src/widgets/header/ui/index.tsx new file mode 100644 index 000000000..47d60fc26 --- /dev/null +++ b/console/ui/src/widgets/header/ui/index.tsx @@ -0,0 +1,64 @@ +import { FC, useEffect } from 'react'; +import { AppBar, Box, MenuItem, SelectChangeEvent, Stack, TextField, Toolbar, Typography } from '@mui/material'; +import Logo from '@shared/assets/PGCLogo.svg?react'; +import { grey } from '@mui/material/colors'; +import LogoutButton from '@features/logout-button'; +import { useGetProjectsQuery } from '@shared/api/api/projects.ts'; +import { setProject } from '@app/redux/slices/projectSlice/projectSlice.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import { useAppDispatch, useAppSelector } from '@app/redux/store/hooks.ts'; +import { useTranslation } from 'react-i18next'; + +const Header: FC = () => { + const { t } = useTranslation('shared'); + const dispatch = useAppDispatch(); + const currentProject = useAppSelector(selectCurrentProject); + + const projects = useGetProjectsQuery({ limit: 999_999_999 }); + + useEffect(() => { + if (!currentProject && projects.data?.data) dispatch(setProject(String(projects.data?.data?.[0]?.id))); + }, [projects.data?.data, dispatch, currentProject]); + + const handleProjectChange = (e: SelectChangeEvent) => { + dispatch(setProject(e.target.value)); + }; + + return ( + theme.zIndex.drawer + 1 }} elevation={0} variant="outlined"> + + + + + + + + PostgreSQL Cluster + + + Console + + + + + {projects.data?.data?.map((project) => ( + + {project.name} + + )) ?? []} + + + + + + + ); +}; + +export default Header; diff --git a/console/ui/src/widgets/main/index.ts b/console/ui/src/widgets/main/index.ts new file mode 100644 index 000000000..cf887abaa --- /dev/null +++ b/console/ui/src/widgets/main/index.ts @@ -0,0 +1,3 @@ +import Main from '@widgets/main/ui'; + +export default Main; diff --git a/console/ui/src/widgets/main/ui/index.tsx b/console/ui/src/widgets/main/ui/index.tsx new file mode 100644 index 000000000..4ea8fab27 --- /dev/null +++ b/console/ui/src/widgets/main/ui/index.tsx @@ -0,0 +1,22 @@ +import { FC, Suspense } from 'react'; +import { Box, Divider, Toolbar } from '@mui/material'; +import { Outlet } from 'react-router-dom'; +import Breadcrumbs from '@features/bradcrumbs'; +import Spinner from '@shared/ui/spinner'; + +const Main: FC = () => ( +
+ + + + + + }> + + + + +
+); + +export default Main; diff --git a/console/ui/src/widgets/operations-table/index.ts b/console/ui/src/widgets/operations-table/index.ts new file mode 100644 index 000000000..e6de8fafd --- /dev/null +++ b/console/ui/src/widgets/operations-table/index.ts @@ -0,0 +1,3 @@ +import OperationsTable from '@widgets/operations-table/ui'; + +export default OperationsTable; diff --git a/console/ui/src/widgets/operations-table/lib/hooks.tsx b/console/ui/src/widgets/operations-table/lib/hooks.tsx new file mode 100644 index 000000000..e1223a7c3 --- /dev/null +++ b/console/ui/src/widgets/operations-table/lib/hooks.tsx @@ -0,0 +1,18 @@ +import { useMemo } from 'react'; +import { OPERATIONS_TABLE_COLUMN_NAMES } from '@widgets/operations-table/model/constants.ts'; +import { ResponseOperation } from '@shared/api/api/operations.ts'; + +export const useGetOperationsTableData = (data: ResponseOperation[]) => + useMemo( + () => + data?.map((operation) => ({ + [OPERATIONS_TABLE_COLUMN_NAMES.ID]: operation.id!, + [OPERATIONS_TABLE_COLUMN_NAMES.CLUSTER]: operation.cluster_name!, + [OPERATIONS_TABLE_COLUMN_NAMES.STARTED]: operation.started, + [OPERATIONS_TABLE_COLUMN_NAMES.FINISHED]: operation.status === 'in_progress' ? '-' : operation?.finished ?? '-', + [OPERATIONS_TABLE_COLUMN_NAMES.TYPE]: operation.type!, + [OPERATIONS_TABLE_COLUMN_NAMES.STATUS]: operation.status!, + [OPERATIONS_TABLE_COLUMN_NAMES.ENVIRONMENT]: operation.environment!, + })) ?? [], + [data], + ); diff --git a/console/ui/src/widgets/operations-table/model/constants.ts b/console/ui/src/widgets/operations-table/model/constants.ts new file mode 100644 index 000000000..bddc32254 --- /dev/null +++ b/console/ui/src/widgets/operations-table/model/constants.ts @@ -0,0 +1,59 @@ +import { TFunction } from 'i18next'; +import { createMRTColumnHelper } from 'material-react-table'; +import { OperationsTableValues } from '@widgets/operations-table/model/types.ts'; +import { convertTimestampToReadableTime } from '@shared/lib/functions.ts'; + +export const OPERATIONS_TABLE_COLUMN_NAMES = Object.freeze({ + // names are used as sorting params, changes will break sorting + ID: 'id', + STARTED: 'created_at', + FINISHED: 'updated_at', + TYPE: 'type', + STATUS: 'status', + CLUSTER: 'cluster_name', + ENVIRONMENT: 'environment', + ACTIONS: 'actions', +}); + +const columnHelper = createMRTColumnHelper(); + +export const operationTableColumns = (t: TFunction) => [ + columnHelper.accessor(OPERATIONS_TABLE_COLUMN_NAMES.ID, { + header: t('id', { ns: 'shared' }), + size: 80, + grow: true, + visibleInShowHideMenu: false, + }), + columnHelper.accessor(OPERATIONS_TABLE_COLUMN_NAMES.STARTED, { + header: t('started', { ns: 'operations' }), + grow: true, + size: 120, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), + }), + columnHelper.accessor(OPERATIONS_TABLE_COLUMN_NAMES.FINISHED, { + header: t('finished', { ns: 'operations' }), + grow: true, + size: 120, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), + }), + columnHelper.accessor(OPERATIONS_TABLE_COLUMN_NAMES.TYPE, { + header: t('type', { ns: 'operations' }), + grow: true, + size: 60, + }), + columnHelper.accessor(OPERATIONS_TABLE_COLUMN_NAMES.STATUS, { + header: t('status', { ns: 'shared' }), + grow: true, + size: 80, + }), + columnHelper.accessor(OPERATIONS_TABLE_COLUMN_NAMES.CLUSTER, { + header: t('cluster', { ns: 'clusters' }), + grow: true, + size: 140, + }), + columnHelper.accessor(OPERATIONS_TABLE_COLUMN_NAMES.ENVIRONMENT, { + header: t('environment', { ns: 'shared' }), + grow: true, + size: 140, + }), +]; diff --git a/console/ui/src/widgets/operations-table/model/types.ts b/console/ui/src/widgets/operations-table/model/types.ts new file mode 100644 index 000000000..46304e31f --- /dev/null +++ b/console/ui/src/widgets/operations-table/model/types.ts @@ -0,0 +1,11 @@ +import { OPERATIONS_TABLE_COLUMN_NAMES } from '@widgets/operations-table/model/constants.ts'; + +export interface OperationsTableValues { + [OPERATIONS_TABLE_COLUMN_NAMES.ID]: number; + [OPERATIONS_TABLE_COLUMN_NAMES.STARTED]: string; + [OPERATIONS_TABLE_COLUMN_NAMES.FINISHED]: string; + [OPERATIONS_TABLE_COLUMN_NAMES.TYPE]: string; + [OPERATIONS_TABLE_COLUMN_NAMES.STATUS]: string; + [OPERATIONS_TABLE_COLUMN_NAMES.CLUSTER]: string; + [OPERATIONS_TABLE_COLUMN_NAMES.ENVIRONMENT]: string; +} diff --git a/console/ui/src/widgets/operations-table/ui/index.tsx b/console/ui/src/widgets/operations-table/ui/index.tsx new file mode 100644 index 000000000..9c5dc1e22 --- /dev/null +++ b/console/ui/src/widgets/operations-table/ui/index.tsx @@ -0,0 +1,90 @@ +import { FC, useMemo, useState } from 'react'; +import { MRT_ColumnDef, MRT_RowData, MRT_TableOptions } from 'material-react-table'; +import { OPERATIONS_TABLE_COLUMN_NAMES, operationTableColumns } from '@widgets/operations-table/model/constants.ts'; +import { useTranslation } from 'react-i18next'; +import { OperationsTableValues } from '@widgets/operations-table/model/types.ts'; +import OperationsTableButtons from '@features/operations-table-buttons'; +import OperationsTableRowActions from '@features/operations-table-row-actions'; +import { useGetOperationsQuery } from '@shared/api/api/operations.ts'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import { subDays } from 'date-fns/subDays'; +import { + formatOperationsDate, + getOperationsDateRangeVariants, +} from '@features/operations-table-buttons/lib/functions.ts'; +import { OPERATIONS_POLLING_INTERVAL, PAGINATION_LIMIT_OPTIONS } from '@shared/config/constants.ts'; +import { useGetOperationsTableData } from '@widgets/operations-table/lib/hooks.tsx'; +import { manageSortingOrder } from '@shared/lib/functions.ts'; +import { useQueryPolling } from '@shared/lib/hooks.tsx'; +import DefaultTable from '@shared/ui/default-table'; + +const OperationsTable: FC = () => { + const { t, i18n } = useTranslation(['operations', 'shared']); + + const currentProject = useAppSelector(selectCurrentProject); + + const [sorting, setSorting] = useState([ + { + id: OPERATIONS_TABLE_COLUMN_NAMES.ID, + desc: true, + }, + ]); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGINATION_LIMIT_OPTIONS[1].value, + }); + + const [endDate] = useState(new Date().toISOString()); + const [startDate, setStartDate] = useState({ + name: getOperationsDateRangeVariants(t)[0].value, + value: formatOperationsDate(subDays(new Date(), 1)), + }); + + const operationsList = useQueryPolling( + () => + useGetOperationsQuery({ + projectId: Number(currentProject), + startDate: startDate.value, + endDate, + offset: pagination.pageIndex * pagination.pageSize, + limit: pagination.pageSize, + ...(sorting?.[0] ? { sortBy: manageSortingOrder(sorting[0]) } : {}), + }), + OPERATIONS_POLLING_INTERVAL, + ); + + const columns = useMemo[]>(() => operationTableColumns(t), [i18n.language]); + + const data = useGetOperationsTableData(operationsList.data?.data); + + const tableConfig: MRT_TableOptions = { + columns, + data, + enablePagination: true, + showGlobalFilter: true, + manualSorting: true, + manualPagination: true, + enableRowActions: true, + enableStickyHeader: true, + enableMultiSort: false, + onPaginationChange: setPagination, + onSortingChange: setSorting, + rowCount: operationsList.data?.meta?.count ?? 0, + state: { + isLoading: operationsList.isFetching, + pagination, + sorting, + }, + renderRowActionMenuItems: ({ closeMenu, row }) => , + }; + + return ( + <> + + + + ); +}; + +export default OperationsTable; diff --git a/console/ui/src/widgets/projects-table/index.tsx b/console/ui/src/widgets/projects-table/index.tsx new file mode 100644 index 000000000..398bdd3ef --- /dev/null +++ b/console/ui/src/widgets/projects-table/index.tsx @@ -0,0 +1,3 @@ +import ProjectsTable from '@widgets/projects-table/ui'; + +export default ProjectsTable; diff --git a/console/ui/src/widgets/projects-table/lib/hooks.tsx b/console/ui/src/widgets/projects-table/lib/hooks.tsx new file mode 100644 index 000000000..456940f7f --- /dev/null +++ b/console/ui/src/widgets/projects-table/lib/hooks.tsx @@ -0,0 +1,16 @@ +import { useMemo } from 'react'; +import { ResponseProject } from '@shared/api/api/projects.ts'; +import { PROJECTS_TABLE_COLUMN_NAMES } from '@widgets/projects-table/model/constants.ts'; + +export const useGetProjectsTableData = (data: ResponseProject[]) => + useMemo( + () => + data?.map((secret) => ({ + [PROJECTS_TABLE_COLUMN_NAMES.ID]: secret.id, + [PROJECTS_TABLE_COLUMN_NAMES.NAME]: secret.name, + [PROJECTS_TABLE_COLUMN_NAMES.CREATED]: secret.created_at, + [PROJECTS_TABLE_COLUMN_NAMES.UPDATED]: secret.updated_at, + [PROJECTS_TABLE_COLUMN_NAMES.DESCRIPTION]: secret.description ?? '-', + })) ?? [], + [data], + ); diff --git a/console/ui/src/widgets/projects-table/model/constants.ts b/console/ui/src/widgets/projects-table/model/constants.ts new file mode 100644 index 000000000..a9e758097 --- /dev/null +++ b/console/ui/src/widgets/projects-table/model/constants.ts @@ -0,0 +1,44 @@ +import { createMRTColumnHelper } from 'material-react-table'; +import { TFunction } from 'i18next'; +import { convertTimestampToReadableTime } from '@shared/lib/functions.ts'; +import { ProjectsTableValues } from '@widgets/projects-table/model/types.ts'; + +export const PROJECTS_TABLE_COLUMN_NAMES = Object.freeze({ + ID: 'id', + NAME: 'name', + DESCRIPTION: 'description', + CREATED: 'created_at', + UPDATED: 'updated_at', +}); + +const columnHelper = createMRTColumnHelper(); + +export const projectsTableColumns = (t: TFunction) => [ + columnHelper.accessor(PROJECTS_TABLE_COLUMN_NAMES.ID, { + header: t('id', { ns: 'shared' }), + size: 80, + grow: true, + }), + columnHelper.accessor(PROJECTS_TABLE_COLUMN_NAMES.NAME, { + header: t('name', { ns: 'shared' }), + size: 80, + grow: true, + }), + columnHelper.accessor(PROJECTS_TABLE_COLUMN_NAMES.CREATED, { + header: t('created', { ns: 'shared' }), + size: 150, + grow: true, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), // convert back to string for display + }), + columnHelper.accessor(PROJECTS_TABLE_COLUMN_NAMES.UPDATED, { + header: t('updated', { ns: 'shared' }), + size: 150, + grow: true, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), // convert back to string for display + }), + columnHelper.accessor(PROJECTS_TABLE_COLUMN_NAMES.DESCRIPTION, { + header: t('description', { ns: 'shared' }), + size: 150, + grow: true, + }), +]; diff --git a/console/ui/src/widgets/projects-table/model/types.ts b/console/ui/src/widgets/projects-table/model/types.ts new file mode 100644 index 000000000..accbdd8d5 --- /dev/null +++ b/console/ui/src/widgets/projects-table/model/types.ts @@ -0,0 +1,6 @@ +import { PROJECTS_TABLE_COLUMN_NAMES } from '@widgets/projects-table/model/constants.ts'; + +export interface ProjectsTableValues { + [PROJECTS_TABLE_COLUMN_NAMES.ID]: string; + [PROJECTS_TABLE_COLUMN_NAMES.NAME]: string; +} diff --git a/console/ui/src/widgets/projects-table/ui/ProjectsTableButtons.tsx b/console/ui/src/widgets/projects-table/ui/ProjectsTableButtons.tsx new file mode 100644 index 000000000..3875747a2 --- /dev/null +++ b/console/ui/src/widgets/projects-table/ui/ProjectsTableButtons.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import AddProject from '@features/add-project'; +import { Stack } from '@mui/material'; + +const ProjectsTableButtons: FC = () => { + return ( + + + + ); +}; + +export default ProjectsTableButtons; diff --git a/console/ui/src/widgets/projects-table/ui/index.tsx b/console/ui/src/widgets/projects-table/ui/index.tsx new file mode 100644 index 000000000..1820a5a19 --- /dev/null +++ b/console/ui/src/widgets/projects-table/ui/index.tsx @@ -0,0 +1,58 @@ +import React, { FC, useMemo, useState } from 'react'; +import { PAGINATION_LIMIT_OPTIONS } from '@shared/config/constants.ts'; +import { MRT_ColumnDef, MRT_RowData, MRT_TableOptions } from 'material-react-table'; +import { ProjectsTableValues } from '@widgets/projects-table/model/types.ts'; +import { projectsTableColumns } from '@widgets/projects-table/model/constants.ts'; +import { useTranslation } from 'react-i18next'; +import { useGetProjectsTableData } from '@widgets/projects-table/lib/hooks.tsx'; +import { useGetProjectsQuery } from '@shared/api/api/projects.ts'; +import ProjectsTableButtons from '@widgets/projects-table/ui/ProjectsTableButtons.tsx'; +import ProjectsTableRowActions from '@features/pojects-table-row-actions'; +import DefaultTable from '@shared/ui/default-table'; + +const ProjectsTable: FC = () => { + const { t, i18n } = useTranslation(['settings', 'shared']); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGINATION_LIMIT_OPTIONS[1].value, + }); + + const projectsList = useGetProjectsQuery({ + offset: pagination.pageIndex * pagination.pageSize, + limit: pagination.pageSize, + }); + + const columns = useMemo[]>(() => projectsTableColumns(t), [i18n.language]); + + const data = useGetProjectsTableData(projectsList.data?.data); + + const tableConfig: MRT_TableOptions = { + columns, + data, + enablePagination: true, + enableRowSelection: true, + showGlobalFilter: true, + enableRowActions: true, + enableStickyHeader: true, + enableMultiSort: false, + enableSorting: false, + onPaginationChange: setPagination, + manualPagination: true, + rowCount: projectsList.data?.meta?.count ?? 0, + state: { + isLoading: projectsList.isFetching, + pagination, + }, + renderRowActionMenuItems: ({ closeMenu, row }) => , + }; + + return ( + <> + + + + ); +}; + +export default ProjectsTable; diff --git a/console/ui/src/widgets/secrets-table/index.ts b/console/ui/src/widgets/secrets-table/index.ts new file mode 100644 index 000000000..cc40093da --- /dev/null +++ b/console/ui/src/widgets/secrets-table/index.ts @@ -0,0 +1,3 @@ +import SecretsTable from '@widgets/secrets-table/ui'; + +export default SecretsTable; diff --git a/console/ui/src/widgets/secrets-table/lib/hooks.tsx b/console/ui/src/widgets/secrets-table/lib/hooks.tsx new file mode 100644 index 000000000..6fb0035a0 --- /dev/null +++ b/console/ui/src/widgets/secrets-table/lib/hooks.tsx @@ -0,0 +1,18 @@ +import { ResponseSecretInfo } from '@shared/api/api/secrets.ts'; +import { useMemo } from 'react'; +import { SECRETS_TABLE_COLUMN_NAMES } from '@widgets/secrets-table/model/constants.ts'; + +export const useGetSecretsTableData = (data: ResponseSecretInfo[]) => + useMemo( + () => + data?.map((secret) => ({ + [SECRETS_TABLE_COLUMN_NAMES.NAME]: secret.name!, + [SECRETS_TABLE_COLUMN_NAMES.TYPE]: secret.type!, + [SECRETS_TABLE_COLUMN_NAMES.CREATED]: secret.created_at, + [SECRETS_TABLE_COLUMN_NAMES.UPDATED]: secret.updated_at, + [SECRETS_TABLE_COLUMN_NAMES.USED]: String(!!secret.is_used), + [SECRETS_TABLE_COLUMN_NAMES.ID]: secret.id!, + [SECRETS_TABLE_COLUMN_NAMES.USED_BY]: secret.used_by_clusters, // not displayed, required only for logic purposed + })) ?? [], + [data], + ); diff --git a/console/ui/src/widgets/secrets-table/model/constants.ts b/console/ui/src/widgets/secrets-table/model/constants.ts new file mode 100644 index 000000000..be74a900a --- /dev/null +++ b/console/ui/src/widgets/secrets-table/model/constants.ts @@ -0,0 +1,51 @@ +import { TFunction } from 'i18next'; +import { createMRTColumnHelper } from 'material-react-table'; +import { SecretsTableValues } from '@widgets/secrets-table/model/types.ts'; +import { convertTimestampToReadableTime } from '@shared/lib/functions.ts'; + +export const SECRETS_TABLE_COLUMN_NAMES = Object.freeze({ + NAME: 'name', + TYPE: 'type', + CREATED: 'created', + UPDATED: 'updated', + USED: 'used', + ID: 'id', + USED_BY: 'usedBy', +}); + +const columnHelper = createMRTColumnHelper(); + +export const secretsTableColumns = (t: TFunction) => [ + columnHelper.accessor(SECRETS_TABLE_COLUMN_NAMES.NAME, { + header: t('name', { ns: 'shared' }), + size: 80, + grow: true, + }), + columnHelper.accessor(SECRETS_TABLE_COLUMN_NAMES.TYPE, { + header: t('type', { ns: 'shared' }), + size: 80, + grow: true, + }), + columnHelper.accessor(SECRETS_TABLE_COLUMN_NAMES.CREATED, { + header: t('created', { ns: 'shared' }), + size: 150, + grow: true, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), // convert back to string for display + }), + columnHelper.accessor(SECRETS_TABLE_COLUMN_NAMES.UPDATED, { + header: t('updated', { ns: 'shared' }), + size: 150, + grow: true, + Cell: ({ cell }) => convertTimestampToReadableTime(cell.getValue()), // convert back to string for display + }), + columnHelper.accessor(SECRETS_TABLE_COLUMN_NAMES.USED, { + header: t('used', { ns: 'shared' }), + size: 150, + grow: true, + }), + columnHelper.accessor(SECRETS_TABLE_COLUMN_NAMES.ID, { + header: t('id', { ns: 'shared' }), + size: 80, + grow: true, + }), +]; diff --git a/console/ui/src/widgets/secrets-table/model/types.ts b/console/ui/src/widgets/secrets-table/model/types.ts new file mode 100644 index 000000000..4a0a545f5 --- /dev/null +++ b/console/ui/src/widgets/secrets-table/model/types.ts @@ -0,0 +1,10 @@ +import { SECRETS_TABLE_COLUMN_NAMES } from '@widgets/secrets-table/model/constants.ts'; + +export interface SecretsTableValues { + [SECRETS_TABLE_COLUMN_NAMES.NAME]: string; + [SECRETS_TABLE_COLUMN_NAMES.TYPE]: string; + [SECRETS_TABLE_COLUMN_NAMES.CREATED]: string; + [SECRETS_TABLE_COLUMN_NAMES.UPDATED]: string; + [SECRETS_TABLE_COLUMN_NAMES.USED]: string; + [SECRETS_TABLE_COLUMN_NAMES.ID]: number; +} diff --git a/console/ui/src/widgets/secrets-table/ui/index.tsx b/console/ui/src/widgets/secrets-table/ui/index.tsx new file mode 100644 index 000000000..b08ff5c4a --- /dev/null +++ b/console/ui/src/widgets/secrets-table/ui/index.tsx @@ -0,0 +1,64 @@ +import React, { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { MRT_ColumnDef, MRT_RowData, MRT_TableOptions } from 'material-react-table'; +import { SecretsTableValues } from '@widgets/secrets-table/model/types.ts'; +import SettingsTableRowActions from '@features/settings-table-row-actions'; +import SettingsTableButtons from '@features/settings-table-buttons'; +import { useGetSecretsQuery } from '@shared/api/api/secrets.ts'; +import { PAGINATION_LIMIT_OPTIONS } from '@shared/config/constants.ts'; +import { useAppSelector } from '@app/redux/store/hooks.ts'; +import { selectCurrentProject } from '@app/redux/slices/projectSlice/projectSelectors.ts'; +import { secretsTableColumns } from '@widgets/secrets-table/model/constants.ts'; + +import { useGetSecretsTableData } from '@widgets/secrets-table/lib/hooks.tsx'; +import DefaultTable from '@shared/ui/default-table'; + +const SecretsTable: React.FC = () => { + const { t, i18n } = useTranslation(['settings', 'shared']); + + const currentProject = useAppSelector(selectCurrentProject); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGINATION_LIMIT_OPTIONS[1].value, + }); + + const secretsList = useGetSecretsQuery({ + projectId: Number(currentProject), + offset: pagination.pageIndex * pagination.pageSize, + limit: pagination.pageSize, + }); + + const columns = useMemo[]>(() => secretsTableColumns(t), [i18n.language]); + + const data = useGetSecretsTableData(secretsList.data?.data); + + const tableConfig: MRT_TableOptions = { + columns, + data, + enablePagination: true, + enableRowSelection: true, + showGlobalFilter: true, + enableRowActions: true, + enableStickyHeader: true, + enableMultiSort: false, + enableSorting: false, + onPaginationChange: setPagination, + manualPagination: true, + rowCount: secretsList.data?.meta?.count ?? 0, + state: { + isLoading: secretsList.isFetching, + pagination, + }, + renderRowActionMenuItems: ({ closeMenu, row }) => , + }; + + return ( + <> + + + + ); +}; + +export default SecretsTable; diff --git a/console/ui/src/widgets/settings-form/index.ts b/console/ui/src/widgets/settings-form/index.ts new file mode 100644 index 000000000..cefc899ea --- /dev/null +++ b/console/ui/src/widgets/settings-form/index.ts @@ -0,0 +1,3 @@ +import SettingsForm from '@widgets/settings-form/ui'; + +export default SettingsForm; diff --git a/console/ui/src/widgets/settings-form/ui/index.tsx b/console/ui/src/widgets/settings-form/ui/index.tsx new file mode 100644 index 000000000..0d4148e7e --- /dev/null +++ b/console/ui/src/widgets/settings-form/ui/index.tsx @@ -0,0 +1,98 @@ +import { FC, useEffect, useState } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { SettingsFormValues } from '@entities/settings-proxy-block/model/types.ts'; +import { Box, Stack } from '@mui/material'; +import SettingsProxyBlock from '@entities/settings-proxy-block'; +import { useTranslation } from 'react-i18next'; +import { SETTINGS_FORM_FIELDS_NAMES } from '@entities/settings-proxy-block/model/constants.ts'; +import { + useGetSettingsQuery, + usePatchSettingsByNameMutation, + usePostSettingsMutation, +} from '@shared/api/api/settings.ts'; +import { LoadingButton } from '@mui/lab'; +import { toast } from 'react-toastify'; +import { handleRequestErrorCatch } from '@shared/lib/functions.ts'; +import Spinner from '@shared/ui/spinner'; + +const SettingsForm: FC = () => { + const { t } = useTranslation(['shared', 'toasts']); + + const [isResetting, setIsResetting] = useState(false); + + const methods = useForm({ + mode: 'all', + defaultValues: { + [SETTINGS_FORM_FIELDS_NAMES.HTTP_PROXY]: '', + [SETTINGS_FORM_FIELDS_NAMES.HTTPS_PROXY]: '', + }, + }); + + const settings = useGetSettingsQuery({ offset: 0, limit: 999_999_999 }); + const [postSettingsTrigger, postSettingsTriggerState] = usePostSettingsMutation(); + const [patchSettingsTrigger, patchSettingsTriggerState] = usePatchSettingsByNameMutation(); + + const { isValid, isDirty } = methods.formState; + + useEffect(() => { + if (settings.isFetching) setIsResetting(true); + if (settings.data?.data) { + // eslint-disable-next-line @typescript-eslint/require-await + const resetForm = async () => { + // sync function will result in form values setting error + const settingsData = settings.data.data?.find((value) => value.name === 'proxy_env')?.value; + methods.reset((values) => ({ + ...values, + ...settingsData, + })); + }; + void resetForm().then(() => setIsResetting(false)); + } + }, [settings.data?.data, methods]); + + const onSubmit = async (values: SettingsFormValues) => { + try { + const filledFormValues = Object.fromEntries(Object.entries(values).filter(([_, value]) => value !== '')); + settings.data?.data?.find((value) => value?.name === 'proxy_env')?.value && isDirty + ? await patchSettingsTrigger({ + name: 'proxy_env', + requestChangeSetting: { value: { ...filledFormValues } }, + }).unwrap() + : await postSettingsTrigger({ + requestCreateSetting: { + name: 'proxy_env', + value: { ...filledFormValues }, + }, + }).unwrap(); + toast.success(t('settingsSuccessfullyChanged', { ns: 'toasts' })); + methods.reset(values); + } catch (e) { + handleRequestErrorCatch(e); + } + }; + + return ( + + {isResetting || settings.isFetching ? ( + + ) : ( + +
+ + + + {t('save')} + + +
+
+ )} +
+ ); +}; + +export default SettingsForm; diff --git a/console/ui/src/widgets/sidebar/index.ts b/console/ui/src/widgets/sidebar/index.ts new file mode 100644 index 000000000..0ee5979a3 --- /dev/null +++ b/console/ui/src/widgets/sidebar/index.ts @@ -0,0 +1,3 @@ +import Sidebar from './ui'; + +export default Sidebar; diff --git a/console/ui/src/widgets/sidebar/model/constants.ts b/console/ui/src/widgets/sidebar/model/constants.ts new file mode 100644 index 000000000..79f8ab375 --- /dev/null +++ b/console/ui/src/widgets/sidebar/model/constants.ts @@ -0,0 +1,54 @@ +import { TFunction } from 'i18next'; +import RouterPaths from '@app/router/routerPathsConfig'; +import ClustersIcon from '@assets/clustersIcon.svg?react'; +import OperationsIcon from '@assets/operationsIcon.svg?react'; +import SettingsIcon from '@assets/settingsIcon.svg?react'; +import GithubIcon from '@assets/githubIcon.svg?react'; +import DocumentationIcon from '@assets/docsIcon.svg?react'; +import SupportIcon from '@assets/supportIcon.svg?react'; +import SponsorIcon from '@assets/sponsorIcon.svg?react'; + +export const sidebarData = (t: TFunction) => [ + { + icon: ClustersIcon, + label: t('clusters', { ns: 'clusters' }), + path: RouterPaths.clusters.absolutePath, + }, + { + icon: OperationsIcon, + label: t('operations', { ns: 'operations' }), + path: RouterPaths.operations.absolutePath, + }, + { + icon: SettingsIcon, + label: t('settings', { ns: 'settings' }), + path: RouterPaths.settings.absolutePath, + }, +]; + +export const sidebarLowData = (t: TFunction) => [ + { + icon: GithubIcon, + label: t('github', { ns: 'shared' }), + path: 'https://github.com/vitabaks/postgresql_cluster', + }, + { + icon: DocumentationIcon, + label: t('documentation', { ns: 'shared' }), + path: 'https://postgresql-cluster.org', + }, + { + icon: SupportIcon, + label: t('support', { ns: 'shared' }), + path: 'https://github.com/vitabaks/postgresql_cluster/issues', + }, + { + icon: SponsorIcon, + label: t('sponsor', { ns: 'shared' }), + path: 'https://github.com/vitabaks/postgresql_cluster?tab=readme-ov-file#sponsor-this-project', + }, +]; + +export const OPEN_SIDEBAR_WIDTH = '240px'; + +export const COLLAPSED_SIDEBAR_WIDTH = '61px'; diff --git a/console/ui/src/widgets/sidebar/ui/index.tsx b/console/ui/src/widgets/sidebar/ui/index.tsx new file mode 100644 index 000000000..79144bd95 --- /dev/null +++ b/console/ui/src/widgets/sidebar/ui/index.tsx @@ -0,0 +1,86 @@ +import { COLLAPSED_SIDEBAR_WIDTH, OPEN_SIDEBAR_WIDTH, sidebarData, sidebarLowData } from '../model/constants.ts'; +import SidebarItem from '@entities/sidebar-item'; +import { useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router-dom'; +import { Box, Divider, Drawer, IconButton, List, Stack, Toolbar, useMediaQuery } from '@mui/material'; +import { useEffect, useState } from 'react'; +import CollapseIcon from '@shared/assets/collapseIcon.svg?react'; + +const Sidebar = () => { + const { t } = useTranslation('shared'); + const location = useLocation(); + + const [isCollapsed, setIsCollapsed] = useState(localStorage.getItem('isSidebarCollapsed')?.toString() === 'true'); + + const isLesserThan1600 = useMediaQuery('(max-width: 1600px)'); + + const toggleSidebarCollapse = () => { + setIsCollapsed((prev) => { + const newValue = !prev; + localStorage.setItem('isSidebarCollapsed', newValue); + return newValue; + }); + }; + + const isActive = (path: string) => { + return location.pathname?.includes(path); + }; + + useEffect(() => { + if ((!isCollapsed && isLesserThan1600) || (isCollapsed && !isLesserThan1600)) toggleSidebarCollapse(); + }, [isLesserThan1600]); + + const sidebarItems = sidebarData(t).map((item) => ( + + )); + + const sidebarLowIcons = sidebarLowData(t).map((item) => ( + + )); + + return ( + + + + {sidebarItems} + + + {sidebarLowIcons} + + + + + + + ); +}; + +export default Sidebar; diff --git a/console/ui/tsconfig.json b/console/ui/tsconfig.json new file mode 100644 index 000000000..0604aa23a --- /dev/null +++ b/console/ui/tsconfig.json @@ -0,0 +1,61 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strictNullChecks": true, + "jsx": "react-jsx", + "baseUrl": "./", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": [ + "./src/*" + ], + "@app/*": [ + "./src/app/*" + ], + "@assets/*": [ + "./src/shared/assets/*" + ], + "@entities/*": [ + "./src/entities/*" + ], + "@features/*": [ + "./src/features/*" + ], + "@pages/*": [ + "./src/pages/*" + ], + "@widgets/*": [ + "./src/widgets/*" + ], + "@shared/*": [ + "./src/shared/*" + ] + } + }, + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/console/ui/tsconfig.node.json b/console/ui/tsconfig.node.json new file mode 100644 index 000000000..97ede7ee6 --- /dev/null +++ b/console/ui/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/console/ui/vite.config.mts b/console/ui/vite.config.mts new file mode 100644 index 000000000..c1c2bebd5 --- /dev/null +++ b/console/ui/vite.config.mts @@ -0,0 +1,28 @@ +import {defineConfig} from 'vite'; +import react from '@vitejs/plugin-react-swc'; +import svgr from 'vite-plugin-svgr'; +import {resolve} from 'path'; +import fixReactVirtualized from 'esbuild-plugin-react-virtualized' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svgr(), react()], + optimizeDeps: { + exclude: ['js-big-decimal'], + esbuildOptions: { + plugins: [fixReactVirtualized], + }, + }, + resolve: { + alias: { + '@': resolve(__dirname, './src'), + '@app': resolve(__dirname, './src/app'), + '@assets': resolve(__dirname, './src/shared/assets'), + '@entities': resolve(__dirname, './src/entities'), + '@features': resolve(__dirname, './src/features'), + '@pages': resolve(__dirname, './src/pages'), + '@widgets': resolve(__dirname, './src/widgets'), + '@shared': resolve(__dirname, './src/shared'), + }, + }, +}); diff --git a/console/ui/yarn.lock b/console/ui/yarn.lock new file mode 100644 index 000000000..98906fa1d --- /dev/null +++ b/console/ui/yarn.lock @@ -0,0 +1,5015 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@adobe/css-tools@^4.3.2": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" + integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@apidevtools/json-schema-ref-parser@9.0.6": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" + integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg== + dependencies: + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + js-yaml "^3.13.1" + +"@apidevtools/openapi-schemas@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17" + integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== + +"@apidevtools/swagger-methods@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" + integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== + +"@apidevtools/swagger-parser@^10.0.2", "@apidevtools/swagger-parser@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz#a987d71e5be61feb623203be0c96e5985b192ab6" + integrity sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw== + dependencies: + "@apidevtools/json-schema-ref-parser" "9.0.6" + "@apidevtools/openapi-schemas" "^2.1.0" + "@apidevtools/swagger-methods" "^3.0.2" + "@jsdevtools/ono" "^7.1.3" + ajv "^8.6.3" + ajv-draft-04 "^1.0.0" + call-me-maybe "^1.0.1" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/compat-data@^7.23.5": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== + +"@babel/core@^7.21.3": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a" + integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.24.5" + "@babel/helpers" "^7.24.5" + "@babel/parser" "^7.24.5" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.5" + "@babel/types" "^7.24.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/core@^7.23.5": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" + integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.4" + "@babel/parser" "^7.24.4" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.24.1", "@babel/generator@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.4.tgz#1fc55532b88adf952025d5d2d1e71f946cb1c498" + integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/generator@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" + integrity sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA== + dependencies: + "@babel/types" "^7.24.5" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.3": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-module-transforms@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545" + integrity sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.24.3" + "@babel/helper-simple-access" "^7.24.5" + "@babel/helper-split-export-declaration" "^7.24.5" + "@babel/helper-validator-identifier" "^7.24.5" + +"@babel/helper-plugin-utils@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-simple-access@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba" + integrity sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ== + dependencies: + "@babel/types" "^7.24.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" + integrity sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q== + dependencies: + "@babel/types" "^7.24.5" + +"@babel/helper-string-parser@^7.23.4", "@babel/helper-string-parser@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-identifier@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" + integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" + integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + +"@babel/helpers@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a" + integrity sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.5" + "@babel/types" "^7.24.5" + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1", "@babel/parser@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" + integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== + +"@babel/parser@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" + integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== + +"@babel/plugin-transform-react-jsx-self@^7.23.3": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz#a21d866d8167e752c6a7c4555dba8afcdfce6268" + integrity sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-react-jsx-source@^7.23.3": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz#a2dedb12b09532846721b5df99e52ef8dc3351d0" + integrity sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.0", "@babel/runtime@^7.24.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" + integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.7.2": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== + dependencies: + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8" + integrity sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA== + dependencies: + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.24.5" + "@babel/parser" "^7.24.5" + "@babel/types" "^7.24.5" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@babel/types@^7.21.3", "@babel/types@^7.24.5": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" + integrity sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ== + dependencies: + "@babel/helper-string-parser" "^7.24.1" + "@babel/helper-validator-identifier" "^7.24.5" + to-fast-properties "^2.0.0" + +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + +"@emotion/is-prop-valid@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/react@^11.11.4": + version "11.11.4" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" + integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3", "@emotion/serialize@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.4.tgz#fc8f6d80c492cfa08801d544a05331d1cc7cd451" + integrity sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + +"@emotion/styled@^11.11.5": + version "11.11.5" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.5.tgz#0c5c8febef9d86e8a926e663b2e5488705545dfb" + integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/is-prop-valid" "^1.2.2" + "@emotion/serialize" "^1.1.4" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@exodus/schemasafe@^1.0.0-rc.2": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@exodus/schemasafe/-/schemasafe-1.3.0.tgz#731656abe21e8e769a7f70a4d833e6312fe59b7f" + integrity sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw== + +"@faker-js/faker@^8.4.1": + version "8.4.1" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451" + integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg== + +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== + dependencies: + "@floating-ui/utils" "^0.2.1" + +"@floating-ui/dom@^1.0.0": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" + integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/react-dom@^2.0.8": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.9.tgz#264ba8b061000baa132b5910f0427a6acf7ad7ce" + integrity sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + +"@fontsource/roboto@^5.0.13": + version "5.0.13" + resolved "https://registry.yarnpkg.com/@fontsource/roboto/-/roboto-5.0.13.tgz#2d6ec431a2f9dfe38ca76525c2d6bf12241f575b" + integrity sha512-j61DHjsdUCKMXSdNLTOxcG701FWnF0jcqNNQi2iPCDxU8seN/sMxeh62dC++UiagCWq9ghTypX+Pcy7kX+QOeQ== + +"@hookform/resolvers@^3.4.2": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.4.2.tgz#b69525248c2a9a1b2546411251ea25029915841a" + integrity sha512-1m9uAVIO8wVf7VCDAGsuGA0t6Z3m6jVGAN50HkV9vYLl0yixKK/Z1lr01vaRvYCkIKGoy1noVRxMzQYb4y/j1Q== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + +"@mattiasbuelens/web-streams-polyfill@^0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@mattiasbuelens/web-streams-polyfill/-/web-streams-polyfill-0.2.1.tgz#d7c4aa94f98084ec0787be084d47167d62ea5f67" + integrity sha512-oKuFCQFa3W7Hj7zKn0+4ypI8JFm4ZKIoncwAC6wd5WwFW2sL7O1hpPoJdSWpynQ4DJ4lQ6MvFoVDmCLilonDFg== + dependencies: + "@types/whatwg-streams" "^0.0.7" + +"@monaco-editor/loader@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558" + integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg== + dependencies: + state-local "^1.0.6" + +"@monaco-editor/react@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119" + integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw== + dependencies: + "@monaco-editor/loader" "^1.4.0" + +"@mui/base@5.0.0-beta.40", "@mui/base@^5.0.0-beta.40": + version "5.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" + integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" + prop-types "^15.8.1" + +"@mui/core-downloads-tracker@^5.15.17": + version "5.15.17" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.17.tgz#ce8f3dff6ec11c8294d346997f6065eb23fa99be" + integrity sha512-DVAejDQkjNnIac7MfP8sLzuo7fyrBPxNdXe+6bYqOqg1z2OPTlfFAejSNzWe7UenRMuFu9/AyFXj/X2vN2w6dA== + +"@mui/icons-material@^5.15.17": + version "5.15.17" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.17.tgz#518c02354036f7df28c8f9890b1db6a3269fcc2f" + integrity sha512-xVzl2De7IY36s/keHX45YMiCpsIx3mNv2xwDgtBkRSnZQtVk+Gqufwj1ktUxEyjzEhBl0+PiNJqYC31C+n1n6A== + dependencies: + "@babel/runtime" "^7.23.9" + +"@mui/lab@^5.0.0-alpha.170": + version "5.0.0-alpha.170" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.170.tgz#4519dfc8d1c51ca54fb9d8b91b95a3733d07be16" + integrity sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.40" + "@mui/system" "^5.15.15" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + clsx "^2.1.0" + prop-types "^15.8.1" + +"@mui/material@^5.15.17": + version "5.15.17" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.17.tgz#1e30bacc940573813cc418aebd4484708a407ba6" + integrity sha512-ru/MLvTkCh0AZXmqwIpqGTOoVBS/sX48zArXq/DvktxXZx4fskiRA2PEc7Rk5ZlFiZhKh4moL4an+l8zZwq49Q== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.40" + "@mui/core-downloads-tracker" "^5.15.17" + "@mui/system" "^5.15.15" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + react-is "^18.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee" + integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.15.14" + prop-types "^15.8.1" + +"@mui/styled-engine@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" + integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw== + dependencies: + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/system@^5.15.14", "@mui/system@^5.15.15": + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.15.tgz#658771b200ce3c4a0f28e58169f02e5e718d1c53" + integrity sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.15.14" + "@mui/styled-engine" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.2.14": + version "7.2.14" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" + integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== + +"@mui/utils@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a" + integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA== + dependencies: + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.11" + prop-types "^15.8.1" + react-is "^18.2.0" + +"@mui/x-data-grid@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-7.4.0.tgz#1901f2908aca760146ccae74b064fc15462bcf63" + integrity sha512-ILu0AVqqHQf4wN/nblsJ/k7PkvlB115vQ/FEiYk7neZlc/kOJOUyst3MWMVClAecZ8+JEs476q40xd4r1CtMfw== + dependencies: + "@babel/runtime" "^7.24.0" + "@mui/system" "^5.15.14" + "@mui/utils" "^5.15.14" + clsx "^2.1.1" + prop-types "^15.8.1" + reselect "^4.1.8" + +"@mui/x-date-pickers@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-7.5.0.tgz#3d1ce784079e874196007f853108183a5660a1b8" + integrity sha512-azm9AX36/XzllKtfyHn8u8iYDsxf425/LacP4oVaCeQQgIasajSRFxU/g8vxpNWwgTuzIeWwKjj8cvTc/2UBAw== + dependencies: + "@babel/runtime" "^7.24.5" + "@mui/base" "^5.0.0-beta.40" + "@mui/system" "^5.15.14" + "@mui/utils" "^5.15.14" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@reduxjs/toolkit@^2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.2.6.tgz#4a8356dad9d0c1ab255607a555d492168e0e3bc1" + integrity sha512-kH0r495c5z1t0g796eDQAkYbEQ3a1OLYN9o8jQQVZyKyw367pfRGS+qZLkHYvFHiUUdafpoSlQ2QYObIApjPWA== + dependencies: + immer "^10.0.3" + redux "^5.0.1" + redux-thunk "^3.1.0" + reselect "^5.1.0" + +"@remix-run/router@1.16.0": + version "1.16.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.16.0.tgz#0e10181e5fec1434eb071a9bc4bdaac843f16dcc" + integrity sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q== + +"@rollup/pluginutils@^5.0.5": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" + integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + +"@rollup/rollup-android-arm-eabi@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz#5e8930291f1e5ead7fb1171d53ba5c87718de062" + integrity sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q== + +"@rollup/rollup-android-arm64@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz#ffb84f1359c04ec8a022a97110e18a5600f5f638" + integrity sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w== + +"@rollup/rollup-darwin-arm64@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz#b2fcee8d4806a0b1b9185ac038cc428ddedce9f4" + integrity sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw== + +"@rollup/rollup-darwin-x64@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz#fcb25ccbaa3dd33a6490e9d1c64bab2e0e16b932" + integrity sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz#40d46bdfe667e5eca31bf40047460e326d2e26bb" + integrity sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw== + +"@rollup/rollup-linux-arm-musleabihf@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz#7741df2448c11c56588b50835dbfe91b1a10b375" + integrity sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg== + +"@rollup/rollup-linux-arm64-gnu@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz#0a23b02d2933e4c4872ad18d879890b6a4a295df" + integrity sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w== + +"@rollup/rollup-linux-arm64-musl@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz#e37ef259358aa886cc07d782220a4fb83c1e6970" + integrity sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg== + +"@rollup/rollup-linux-powerpc64le-gnu@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz#8c69218b6de05ee2ba211664a2d2ac1e54e43f94" + integrity sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w== + +"@rollup/rollup-linux-riscv64-gnu@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz#d32727dab8f538d9a4a7c03bcf58c436aecd0139" + integrity sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng== + +"@rollup/rollup-linux-s390x-gnu@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz#d46097246a187d99fc9451fe8393b7155b47c5ec" + integrity sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ== + +"@rollup/rollup-linux-x64-gnu@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz#6356c5a03a4afb1c3057490fc51b4764e109dbc7" + integrity sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA== + +"@rollup/rollup-linux-x64-musl@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz#03a5831a9c0d05877b94653b5ddd3020d3c6fb06" + integrity sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA== + +"@rollup/rollup-win32-arm64-msvc@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz#6cc0db57750376b9303bdb6f5482af8974fcae35" + integrity sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA== + +"@rollup/rollup-win32-ia32-msvc@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz#aea0b7e492bd9ed46971cb80bc34f1eb14e07789" + integrity sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w== + +"@rollup/rollup-win32-x64-msvc@4.16.4": + version "4.16.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz#c09ad9a132ccb5a67c4f211d909323ab1294f95f" + integrity sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A== + +"@rtk-query/codegen-openapi@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@rtk-query/codegen-openapi/-/codegen-openapi-1.2.0.tgz#2c63cbbd80382c4ba6c9fab5e9004efb43637de3" + integrity sha512-Sru3aPHyFC0Tb7jeFh/kVMGBdQUcofb9frrHhjNSRLEoJWsG9fjaioUx3nPT5HZVbdAvAFF4xMWFQNfgJBrAGw== + dependencies: + "@apidevtools/swagger-parser" "^10.0.2" + commander "^6.2.0" + oazapfts "^4.8.0" + prettier "^2.2.1" + semver "^7.3.5" + swagger2openapi "^7.0.4" + typescript "^5.0.0" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + +"@svgr/core@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + +"@svgr/plugin-jsx@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + +"@swc/core-darwin-arm64@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.0.tgz#fd56dedb26ebaaf028cc427d0cec998095a275ac" + integrity sha512-dyA25zQjm3xmMFsRPFgBpSqWSW9TITnkndZkZAiPYLjBxH9oTNMa0l09BePsaqEeXySY++tUgAeYu/9onsHLbg== + +"@swc/core-darwin-x64@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.5.0.tgz#cbbc00bba19c01ecd6f6c952b7c6b722f02ef515" + integrity sha512-cO7kZMMA/fcQIBT31LBzcVNSk3AZGVYLqvEPnJhFImjPm3mGKUd6kWpARUEGR68MyRU2VsWhE6eCjMcM+G7bxw== + +"@swc/core-linux-arm-gnueabihf@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.0.tgz#47316c552d7edd06fcd2585a28574f24a82cc4d3" + integrity sha512-BXaXytS4y9lBFRO6vwA6ovvy1d2ZIzS02i2R1oegoZzzNu89CJDpkYXYS9bId0GvK2m9Q9y2ofoZzKE2Rp3PqQ== + +"@swc/core-linux-arm64-gnu@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.0.tgz#c957fdc1bd24d49c2b063fb37147672c29fb4407" + integrity sha512-Bu4/41pGadXKnRsUbox0ig63xImATVH704oPCXcoOvNGkDyMjWgIAhzIi111vrwFNpj9utabgUE4AtlUa2tAOQ== + +"@swc/core-linux-arm64-musl@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.0.tgz#0416382c54182d2e3f326e422716ac3cf7dbad24" + integrity sha512-lUFFvC8tsepNcTnKEHNrePWanVVef6PQ82Rv9wIeebgGHRUqDh6+CyCqodXez+aKz6NyE/PBIfp0r+jPx4hoJA== + +"@swc/core-linux-x64-gnu@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.0.tgz#08ce35c57a0f58e0015731a2b38efce390b34903" + integrity sha512-c6LegFU1qdyMfk+GzNIOvrX61+mksm21Q01FBnXSy1nf1ACj/a86jmr3zkPl0zpNVHfPOw3Ry1QIuLQKD+67YA== + +"@swc/core-linux-x64-musl@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.0.tgz#73edc03943b2a7a06b14cfd4d195d6c0f953ef70" + integrity sha512-I/V8aWBmfDWwjtM1bS8ASG+6PcO/pVFYyPP5g2ok46Vz1o1MnAUd18mHnWX43nqVJokaW+BD/G4ZMZ+gXRl4zQ== + +"@swc/core-win32-arm64-msvc@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.0.tgz#cd07c068c1a06ad66beb69635481adde2845c396" + integrity sha512-nN685BvI7iM58xabrSOSQHUvIY10pcXh5H9DmS8LeYqG6Dkq7QZ8AwYqqonOitIS5C35MUfhSMLpOTzKoLdUqA== + +"@swc/core-win32-ia32-msvc@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.0.tgz#363fba59da64ccc3576f0525070e26966667b388" + integrity sha512-3YjltmEHljI+TvuDOC4lspUzjBUoB3X5BhftRBprSTJx/czuMl0vdoZKs2Snzb5Eqqesp0Rl8q+iQ1E1oJ6dEA== + +"@swc/core-win32-x64-msvc@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.0.tgz#6183c163076da0da6ce994898bcbd4630dbe7514" + integrity sha512-ZairtCwJsaxnUH85DcYCyGpNb9bUoIm9QXYW+VaEoXwbcB95dTIiJwN0aRxPT8B0B2MNw/CXLqjoPo6sDwz5iw== + +"@swc/core@^1.3.107": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.5.0.tgz#189a7770b0d95aeff8ca56b8763705cc27bae90f" + integrity sha512-fjADAC5gOOX54Rpcr1lF9DHLD+nPD5H/zXLtEgK2Ez3esmogT+LfHzCZtUxqetjvaMChKhQ0Pp0ZB6Hpz/tCbw== + dependencies: + "@swc/counter" "^0.1.2" + "@swc/types" "^0.1.5" + optionalDependencies: + "@swc/core-darwin-arm64" "1.5.0" + "@swc/core-darwin-x64" "1.5.0" + "@swc/core-linux-arm-gnueabihf" "1.5.0" + "@swc/core-linux-arm64-gnu" "1.5.0" + "@swc/core-linux-arm64-musl" "1.5.0" + "@swc/core-linux-x64-gnu" "1.5.0" + "@swc/core-linux-x64-musl" "1.5.0" + "@swc/core-win32-arm64-msvc" "1.5.0" + "@swc/core-win32-ia32-msvc" "1.5.0" + "@swc/core-win32-x64-msvc" "1.5.0" + +"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/types@^0.1.5": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.6.tgz#2f13f748995b247d146de2784d3eb7195410faba" + integrity sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg== + dependencies: + "@swc/counter" "^0.1.3" + +"@tanstack/match-sorter-utils@8.15.1": + version "8.15.1" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz#715e028ff43cf79ece10bd5a757047a1016c3bba" + integrity sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw== + dependencies: + remove-accents "0.5.0" + +"@tanstack/react-table@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.16.0.tgz#92151210ff99d6925353d7a2205735d9c31af48c" + integrity sha512-rKRjnt8ostqN2fercRVOIH/dq7MAmOENCMvVlKx6P9Iokhh6woBGnIZEkqsY/vEJf1jN3TqLOb34xQGLVRuhAg== + dependencies: + "@tanstack/table-core" "8.16.0" + +"@tanstack/react-virtual@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.3.0.tgz#5a282efc1ed8da3d4e9e0f9b0c512f735d6c4b5f" + integrity sha512-QFxmTSZBniq15S0vSZ55P4ToXquMXwJypPXyX/ux7sYo6a2FX3/zWoRLLc4eIOGWTjvzqcIVNKhcuFb+OZL3aQ== + dependencies: + "@tanstack/virtual-core" "3.3.0" + +"@tanstack/table-core@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.16.0.tgz#7b58018dd3cec8e0015fe22d6bb24d18d33c891f" + integrity sha512-dCG8vQGk4js5v88/k83tTedWOwjGnIyONrKpHpfmSJB8jwFHl8GSu1sBBxbtACVAPtAQgwNxl0rw1d3RqRM1Tg== + +"@tanstack/virtual-core@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.3.0.tgz#1bf72d51f269c5a0e3ac872c6b57116767f42c25" + integrity sha512-A0004OAa1FcUkPHeeGoKgBrAgjH+uHdDPrw1L7RpkwnODYqRvoilqsHPs8cyTjMg1byZBbiNpQAq2TlFLIaQag== + +"@testing-library/dom@^10.0.0", "@testing-library/dom@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.1.0.tgz#2d073e49771ad614da999ca48f199919e5176fb6" + integrity sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.3.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^6.4.5": + version "6.4.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.5.tgz#badb40296477149136dabef32b572ddd3b56adf1" + integrity sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A== + dependencies: + "@adobe/css-tools" "^4.3.2" + "@babel/runtime" "^7.9.2" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + +"@testing-library/react@^15.0.6": + version "15.0.6" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-15.0.6.tgz#76be2e9e6da98c044823dfbc9d62ad3f10a3a401" + integrity sha512-UlbazRtEpQClFOiYp+1BapMT+xyqWMnE+hh9tn5DQ6gmlE7AIZWcGpzZukmDZuFk3By01oiqOf8lRedLS4k6xQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^10.0.0" + "@types/react-dom" "^18.0.0" + +"@testing-library/user-event@^14.5.2": + version "14.5.2" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" + integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== + +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" + integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== + dependencies: + "@babel/types" "^7.20.7" + +"@types/estree@1.0.5", "@types/estree@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@^20.12.10": + version "20.12.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.10.tgz#8f0c3f12b0f075eee1fe20c1afb417e9765bef76" + integrity sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw== + dependencies: + undici-types "~5.26.4" + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/prop-types@*", "@types/prop-types@^15.7.11": + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== + +"@types/react-dom@^18.0.0": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== + dependencies: + "@types/react" "*" + +"@types/react-dom@^18.2.22": + version "18.2.25" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.25.tgz#2946a30081f53e7c8d585eb138277245caedc521" + integrity sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA== + dependencies: + "@types/react" "*" + +"@types/react-lazylog@^4.5.4": + version "4.5.4" + resolved "https://registry.yarnpkg.com/@types/react-lazylog/-/react-lazylog-4.5.4.tgz#dc1a7ad962538ce564f7c5f5aaa01af464bf020d" + integrity sha512-HYP+lVRyE0c+fGT+IGHMqzQS5X9I7oaQ3iZczor2MQyLUXyAZRv2AJoEcYjH1QNPDIc+vMBSteyuSuw8tkGJ5Q== + dependencies: + "@types/react" "*" + immutable ">=3.8.2" + +"@types/react-transition-group@^4.4.10": + version "4.4.10" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" + integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18.2.66": + version "18.2.79" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.79.tgz#c40efb4f255711f554d47b449f796d1c7756d865" + integrity sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/semver@^7.5.8": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + +"@types/whatwg-streams@^0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@types/whatwg-streams/-/whatwg-streams-0.0.7.tgz#28bfe73dc850562296367249c4b32a50db81e9d3" + integrity sha512-6sDiSEP6DWcY2ZolsJ2s39ZmsoGQ7KVwBDI3sESQsEm9P2dHTcqnDIHRZFRNtLCzWp7hCFGqYbw5GyfpQnJ01A== + +"@typescript-eslint/eslint-plugin@^7.2.0": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz#50a9044e3e5fe76b22caf64fb7fc1f97614bdbfd" + integrity sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.7.1" + "@typescript-eslint/type-utils" "7.7.1" + "@typescript-eslint/utils" "7.7.1" + "@typescript-eslint/visitor-keys" "7.7.1" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^7.2.0": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.7.1.tgz#f940e9f291cdca40c46cb75916217d3a42d6ceea" + integrity sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw== + dependencies: + "@typescript-eslint/scope-manager" "7.7.1" + "@typescript-eslint/types" "7.7.1" + "@typescript-eslint/typescript-estree" "7.7.1" + "@typescript-eslint/visitor-keys" "7.7.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz#07fe59686ca843f66e3e2b5c151522bc38effab2" + integrity sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA== + dependencies: + "@typescript-eslint/types" "7.7.1" + "@typescript-eslint/visitor-keys" "7.7.1" + +"@typescript-eslint/type-utils@7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz#2f8094edca3bebdaad009008929df645ed9c8743" + integrity sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q== + dependencies: + "@typescript-eslint/typescript-estree" "7.7.1" + "@typescript-eslint/utils" "7.7.1" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.7.1.tgz#f903a651fb004c75add08e4e9e207f169d4b98d7" + integrity sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w== + +"@typescript-eslint/typescript-estree@7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz#5cafde48fe390fe1c1b329b2ce0ba8a73c1e87b2" + integrity sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ== + dependencies: + "@typescript-eslint/types" "7.7.1" + "@typescript-eslint/visitor-keys" "7.7.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.7.1.tgz#5d161f2b4a55e1bc38b634bebb921e4bd4e4a16e" + integrity sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.15" + "@types/semver" "^7.5.8" + "@typescript-eslint/scope-manager" "7.7.1" + "@typescript-eslint/types" "7.7.1" + "@typescript-eslint/typescript-estree" "7.7.1" + semver "^7.6.0" + +"@typescript-eslint/visitor-keys@7.7.1": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz#da2294796220bb0f3b4add5ecbb1b9c3f4f65798" + integrity sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw== + dependencies: + "@typescript-eslint/types" "7.7.1" + eslint-visitor-keys "^3.4.3" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vitejs/plugin-react-swc@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.6.0.tgz#dc9cd1363baf3780f3ad3e0a12a46a3ffe0c7526" + integrity sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g== + dependencies: + "@swc/core" "^1.3.107" + +"@vitejs/plugin-react@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz#744d8e4fcb120fc3dbaa471dadd3483f5a304bb9" + integrity sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ== + dependencies: + "@babel/core" "^7.23.5" + "@babel/plugin-transform-react-jsx-self" "^7.23.3" + "@babel/plugin-transform-react-jsx-source" "^7.23.3" + "@types/babel__core" "^7.20.5" + react-refresh "^0.14.0" + +"@vitest/expect@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30" + integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ== + dependencies: + "@vitest/spy" "1.6.0" + "@vitest/utils" "1.6.0" + chai "^4.3.10" + +"@vitest/runner@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825" + integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg== + dependencies: + "@vitest/utils" "1.6.0" + p-limit "^5.0.0" + pathe "^1.1.1" + +"@vitest/snapshot@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470" + integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ== + dependencies: + magic-string "^0.30.5" + pathe "^1.1.1" + pretty-format "^29.7.0" + +"@vitest/spy@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d" + integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw== + dependencies: + tinyspy "^2.2.0" + +"@vitest/utils@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1" + integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw== + dependencies: + diff-sequences "^29.6.3" + estree-walker "^3.0.3" + loupe "^2.3.7" + pretty-format "^29.7.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + +acorn@^8.11.3, acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + +ajv-draft-04@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" + integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.6.3: + version "8.14.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.14.0.tgz#f514ddfd4756abb200e1704414963620a625ebbb" + integrity sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA== + dependencies: + fast-deep-equal "^3.1.3" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.4.1" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@5.3.0, aria-query@^5.0.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-includes@^3.1.6, array-includes@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlast@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.toreversed@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" + integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.tosorted@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" + integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.1.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +autoprefixer@^10.4.19: + version "10.4.19" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" + integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== + dependencies: + browserslist "^4.23.0" + caniuse-lite "^1.0.30001599" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.22.2, browserslist@^4.23.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +call-me-maybe@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: + version "1.0.30001612" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz#d34248b4ec1f117b70b24ad9ee04c90e0b8a14ae" + integrity sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g== + +chai@^4.3.10: + version "4.4.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" + integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + +"chokidar@>=3.0.0 <4.0.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clsx@^1.0.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + +clsx@^2.1.0, clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +confbox@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.7.tgz#ccfc0a2bcae36a84838e83a3b7f770fb17d6c579" + integrity sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA== + +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cosmiconfig@^8.1.3: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cross-fetch@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +cssstyle@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.0.1.tgz#ef29c598a1e90125c870525490ea4f354db0660a" + integrity sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ== + dependencies: + rrweb-cssom "^0.6.0" + +csstype@^3.0.2, csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + dependencies: + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +date-fns@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf" + integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== + +debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + +deep-eql@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" + integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== + dependencies: + type-detect "^4.0.0" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + +dom-helpers@^5.0.1, dom-helpers@^5.1.3: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +electron-to-chromium@^1.4.668: + version "1.4.747" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz#e37fa5b7b7e4c22607c5f59b5cf78f947266e77d" + integrity sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-iterator-helpers@^1.0.17: + version "1.0.18" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" + integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-promise@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" + integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg== + +esbuild-plugin-react-virtualized@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/esbuild-plugin-react-virtualized/-/esbuild-plugin-react-virtualized-1.0.4.tgz#b8911ce8fae4636daa87cfa898752170f5d45609" + integrity sha512-/Y+82TBduHox0/uhJlTgUqi3ZWN+qZPF0xy9crkHQE2AOOdm76l6VY2F0Mdfvue9hqXz2FOlKHlHUVXNalHLzA== + +esbuild-runner@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/esbuild-runner/-/esbuild-runner-2.2.2.tgz#4243089f14c9690bff70beee16da3c41fd1dec50" + integrity sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw== + dependencies: + source-map-support "0.5.21" + tslib "2.4.0" + +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-react-hooks@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + +eslint-plugin-react-refresh@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.6.tgz#e8e8accab681861baed00c5c12da70267db0936f" + integrity sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA== + +eslint-plugin-react@^7.34.1: + version "7.34.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" + integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlast "^1.2.4" + array.prototype.flatmap "^1.3.2" + array.prototype.toreversed "^1.1.2" + array.prototype.tosorted "^1.1.3" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.17" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.7" + object.fromentries "^2.0.7" + object.hasown "^1.1.3" + object.values "^1.1.7" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.10" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.57.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-safe-stringify@^2.0.7: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fetch-readablestream@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/fetch-readablestream/-/fetch-readablestream-0.2.0.tgz#eaa6d1a76b12de2d4731a343393c6ccdcfe2c795" + integrity sha512-qu4mXWf4wus4idBIN/kVH+XSer8IZ9CwHP+Pd7DL7TuKNC1hP7ykon4kkBjwJF3EMX2WsFp4hH7gU7CyL7ucXw== + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +highlight-words@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/highlight-words/-/highlight-words-1.2.2.tgz#9875b75d11814d7356b24f23feeb7d77761fa867" + integrity sha512-Mf4xfPXYm8Ay1wTibCrHpNWeR2nUMynMVFkXCi4mbl+TEgmNOe+I4hV7W3OCZcSvzGL6kupaqpfHOemliMTGxQ== + +hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + dependencies: + whatwg-encoding "^3.1.1" + +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +http2-client@^1.2.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/http2-client/-/http2-client-1.3.5.tgz#20c9dc909e3cc98284dd20af2432c524086df181" + integrity sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA== + +https-proxy-agent@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" + integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== + dependencies: + agent-base "^7.0.2" + debug "4" + +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +i18next-browser-languagedetector@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f" + integrity sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw== + dependencies: + "@babel/runtime" "^7.23.2" + +i18next-fs-backend@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.3.1.tgz#0c7d2459ff4a039e2b3228131809fbc0e74ff1a8" + integrity sha512-tvfXskmG/9o+TJ5Fxu54sSO5OkY6d+uMn+K6JiUGLJrwxAVfer+8V3nU8jq3ts9Pe5lXJv4b1N7foIjJ8Iy2Gg== + +i18next-http-backend@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.5.1.tgz#97141b65d860a124b6c9feee181e565c753b0629" + integrity sha512-+rNX1tghdVxdfjfPt0bI1sNg5ahGW9kA7OboG7b4t03Fp69NdDlRIze6yXhIbN8rbHxJ8IP4dzRm/okZ15lkQg== + dependencies: + cross-fetch "4.0.0" + +i18next@^23.11.3: + version "23.11.3" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.11.3.tgz#d269c9c15bae9d90ab291055cfc433089ca5f77b" + integrity sha512-Pq/aSKowir7JM0rj+Wa23Kb6KKDUGno/HjG+wRQu0PxoTbpQ4N89MAT0rFGvXmLkRLNMb1BbBOKGozl01dabzg== + dependencies: + "@babel/runtime" "^7.23.2" + +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +immer@^10.0.3: + version "10.1.1" + resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc" + integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== + +immutable@>=3.8.2: + version "4.3.6" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.6.tgz#6a05f7858213238e587fb83586ffa3b4b27f0447" + integrity sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ== + +immutable@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" + integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== + +immutable@^4.0.0: + version "4.3.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" + integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== + +import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +ip-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-5.0.0.tgz#cd313b2ae9c80c07bd3851e12bf4fa4dc5480632" + integrity sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw== + +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.0.tgz#0f893996d6f3ed46df7f0a3b12a03f5fd84223c1" + integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsdom@^24.0.0: + version "24.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-24.0.0.tgz#e2dc04e4c79da368481659818ee2b0cd7c39007c" + integrity sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A== + dependencies: + cssstyle "^4.0.1" + data-urls "^5.0.0" + decimal.js "^10.4.3" + form-data "^4.0.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.2" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.7" + parse5 "^7.1.2" + rrweb-cssom "^0.6.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.3" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + ws "^8.16.0" + xml-name-validator "^5.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +local-pkg@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.0.tgz#093d25a346bae59a99f80e75f6e9d36d7e8c925c" + integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg== + dependencies: + mlly "^1.4.2" + pkg-types "^1.0.3" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.21, lodash@^4.17.4: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +loupe@^2.3.6, loupe@^2.3.7: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + +magic-string@^0.30.5: + version "0.30.10" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" + integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +material-react-table@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/material-react-table/-/material-react-table-2.13.0.tgz#445df17dd266a3177c2a1bb3114bd852f752e3da" + integrity sha512-ds4/cupDsXvoz8K8OpM3UqUyqKoAMkVdvmvP/+ovuWA23fPcjYvFFkUpBxtnZq5GKWM0+SZWzr14KQ1DgKCaFQ== + dependencies: + "@tanstack/match-sorter-utils" "8.15.1" + "@tanstack/react-table" "8.16.0" + "@tanstack/react-virtual" "3.3.0" + highlight-words "1.2.2" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mitt@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz#cb24e6569c806e31bd4e3995787fe38a04fdf90d" + integrity sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw== + +mlly@^1.4.2, mlly@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.0.tgz#587383ae40dda23cadb11c3c3cc972b277724271" + integrity sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ== + dependencies: + acorn "^8.11.3" + pathe "^1.1.2" + pkg-types "^1.1.0" + ufo "^1.5.3" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-fetch-h2@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz#c6188325f9bd3d834020bf0f2d6dc17ced2241ac" + integrity sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg== + dependencies: + http2-client "^1.2.5" + +node-fetch@^2.6.1, node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-readfiles@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/node-readfiles/-/node-readfiles-0.2.0.tgz#dbbd4af12134e2e635c245ef93ffcf6f60673a5d" + integrity sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA== + dependencies: + es6-promise "^3.2.1" + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + +nwsapi@^2.2.7: + version "2.2.9" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.9.tgz#7f3303218372db2e9f27c27766bcfc59ae7e61c6" + integrity sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg== + +oas-kit-common@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/oas-kit-common/-/oas-kit-common-1.0.8.tgz#6d8cacf6e9097967a4c7ea8bcbcbd77018e1f535" + integrity sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ== + dependencies: + fast-safe-stringify "^2.0.7" + +oas-linter@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/oas-linter/-/oas-linter-3.2.2.tgz#ab6a33736313490659035ca6802dc4b35d48aa1e" + integrity sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ== + dependencies: + "@exodus/schemasafe" "^1.0.0-rc.2" + should "^13.2.1" + yaml "^1.10.0" + +oas-resolver@^2.5.6: + version "2.5.6" + resolved "https://registry.yarnpkg.com/oas-resolver/-/oas-resolver-2.5.6.tgz#10430569cb7daca56115c915e611ebc5515c561b" + integrity sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ== + dependencies: + node-fetch-h2 "^2.3.0" + oas-kit-common "^1.0.8" + reftools "^1.1.9" + yaml "^1.10.0" + yargs "^17.0.1" + +oas-schema-walker@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz#74c3cd47b70ff8e0b19adada14455b5d3ac38a22" + integrity sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ== + +oas-validator@^5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/oas-validator/-/oas-validator-5.0.8.tgz#387e90df7cafa2d3ffc83b5fb976052b87e73c28" + integrity sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw== + dependencies: + call-me-maybe "^1.0.1" + oas-kit-common "^1.0.8" + oas-linter "^3.2.2" + oas-resolver "^2.5.6" + oas-schema-walker "^1.1.5" + reftools "^1.1.9" + should "^13.2.1" + yaml "^1.10.0" + +oazapfts@^4.8.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/oazapfts/-/oazapfts-4.12.0.tgz#8a86c5fe5a1237b16b05d06d05815cffa2a2b949" + integrity sha512-hNKRG4eLYceuJuqDDx7Uqsi8p3j5k83gNKSo2qnUOTiiU03sCQOjXxOqCXDbzRcuDFyK94+1PBIpotK4NoxIjw== + dependencies: + "@apidevtools/swagger-parser" "^10.1.0" + lodash "^4.17.21" + minimist "^1.2.8" + swagger2openapi "^7.0.8" + typescript "^5.2.2" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.hasown@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" + integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== + dependencies: + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.values@^1.1.6, object.values@^1.1.7: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-limit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" + integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== + dependencies: + yocto-queue "^1.0.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathe@^1.1.1, pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-types@^1.0.3, pkg-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.1.0.tgz#3ec1bf33379030fd0a34c227b6c650e8ea7ca271" + integrity sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA== + dependencies: + confbox "^0.1.7" + mlly "^1.6.1" + pathe "^1.1.2" + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^2.2.1: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prettier@^3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" + integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== + +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-expr@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-error-boundary@^4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.13.tgz#80386b7b27b1131c5fbb7368b8c0d983354c7947" + integrity sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ== + dependencies: + "@babel/runtime" "^7.12.5" + +react-hook-form@^7.51.5: + version "7.51.5" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.5.tgz#4afbfb819312db9fea23e8237a3a0d097e128b43" + integrity sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q== + +react-i18next@^14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.1.tgz#3d942a99866555ae3552c40f9fddfa061e29d7f3" + integrity sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ== + dependencies: + "@babel/runtime" "^7.23.9" + html-parse-stringify "^3.0.1" + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-is@^18.0.0, react-is@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react-lazylog@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/react-lazylog/-/react-lazylog-4.5.3.tgz#289e24995b5599e75943556ac63f5e2c04d0001e" + integrity sha512-lyov32A/4BqihgXgtNXTHCajXSXkYHPlIEmV8RbYjHIMxCFSnmtdg4kDCI3vATz7dURtiFTvrw5yonHnrS+NNg== + dependencies: + "@mattiasbuelens/web-streams-polyfill" "^0.2.0" + fetch-readablestream "^0.2.0" + immutable "^3.8.2" + mitt "^1.1.2" + prop-types "^15.6.1" + react-string-replace "^0.4.1" + react-virtualized "^9.21.0" + text-encoding-utf-8 "^1.0.1" + whatwg-fetch "^2.0.4" + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-redux@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.1.2.tgz#deba38c64c3403e9abd0c3fbeab69ffd9d8a7e4b" + integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w== + dependencies: + "@types/use-sync-external-store" "^0.0.3" + use-sync-external-store "^1.0.0" + +react-refresh@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== + +react-router-dom@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.23.0.tgz#8b80ad92ad28f4dc38972e92d84b4c208150545a" + integrity sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ== + dependencies: + "@remix-run/router" "1.16.0" + react-router "6.23.0" + +react-router@6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.0.tgz#2f2d7492c66a6bdf760be4c6bdf9e1d672fa154b" + integrity sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA== + dependencies: + "@remix-run/router" "1.16.0" + +react-string-replace@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-0.4.4.tgz#24006fbe0db573d5be583133df38b1a735cb4225" + integrity sha512-FAMkhxmDpCsGTwTZg7p/2v+/GTmxAp73so3fbSvlAcBBX36ujiGRNEaM/1u+jiYQrArhns+7eE92g2pi5E5FUA== + dependencies: + lodash "^4.17.4" + +react-toastify@^10.0.5: + version "10.0.5" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-10.0.5.tgz#6b8f8386060c5c856239f3036d1e76874ce3bd1e" + integrity sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw== + dependencies: + clsx "^2.1.0" + +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react-virtualized@^9.21.0: + version "9.22.5" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.5.tgz#bfb96fed519de378b50d8c0064b92994b3b91620" + integrity sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ== + dependencies: + "@babel/runtime" "^7.7.2" + clsx "^1.0.4" + dom-helpers "^5.1.3" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== + +redux@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + +reflect.getprototypeof@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +reftools@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/reftools/-/reftools-1.1.9.tgz#e16e19f662ccd4648605312c06d34e5da3a2b77e" + integrity sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +remove-accents@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" + integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +reselect@^4.1.8: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== + +reselect@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e" + integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.19.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup@^4.13.0: + version "4.16.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.16.4.tgz#fe328eb41293f20c9593a095ec23bdc4b5d93317" + integrity sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.16.4" + "@rollup/rollup-android-arm64" "4.16.4" + "@rollup/rollup-darwin-arm64" "4.16.4" + "@rollup/rollup-darwin-x64" "4.16.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.16.4" + "@rollup/rollup-linux-arm-musleabihf" "4.16.4" + "@rollup/rollup-linux-arm64-gnu" "4.16.4" + "@rollup/rollup-linux-arm64-musl" "4.16.4" + "@rollup/rollup-linux-powerpc64le-gnu" "4.16.4" + "@rollup/rollup-linux-riscv64-gnu" "4.16.4" + "@rollup/rollup-linux-s390x-gnu" "4.16.4" + "@rollup/rollup-linux-x64-gnu" "4.16.4" + "@rollup/rollup-linux-x64-musl" "4.16.4" + "@rollup/rollup-win32-arm64-msvc" "4.16.4" + "@rollup/rollup-win32-ia32-msvc" "4.16.4" + "@rollup/rollup-win32-x64-msvc" "4.16.4" + fsevents "~2.3.2" + +rrweb-cssom@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" + integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sass@^1.76.0: + version "1.76.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.76.0.tgz#fe15909500735ac154f0dc7386d656b62b03987d" + integrity sha512-nc3LeqvF2FNW5xGF1zxZifdW3ffIz5aBb7I7tSvOoNu7z1RQ6pFt9MBuiPtjgaI62YWrM/txjWlOCFiGtf2xpw== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +semver@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1, set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + integrity sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q== + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a" + integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + integrity sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ== + +should-util@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.1.tgz#fb0d71338f532a3a149213639e2d32cbea8bcb28" + integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g== + +should@^13.2.1: + version "13.2.3" + resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10" + integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== + dependencies: + should-equal "^2.0.0" + should-format "^3.0.3" + should-type "^1.4.0" + should-type-adaptors "^1.0.1" + should-util "^1.0.0" + +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +source-map-support@0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +state-local@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" + integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== + +std-env@^3.5.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.matchall@^4.0.10: + version "4.0.11" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" + +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-2.1.0.tgz#6d82ade5e2e74f5c7e8739b6c84692bd65f0bd2a" + integrity sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw== + dependencies: + js-tokens "^9.0.0" + +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +swagger2openapi@^7.0.4, swagger2openapi@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/swagger2openapi/-/swagger2openapi-7.0.8.tgz#12c88d5de776cb1cbba758994930f40ad0afac59" + integrity sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g== + dependencies: + call-me-maybe "^1.0.1" + node-fetch "^2.6.1" + node-fetch-h2 "^2.3.0" + node-readfiles "^0.2.0" + oas-kit-common "^1.0.8" + oas-resolver "^2.5.6" + oas-schema-walker "^1.1.5" + oas-validator "^5.0.8" + reftools "^1.1.9" + yaml "^1.10.0" + yargs "^17.0.1" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +text-encoding-utf-8@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" + integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + +tinybench@^2.5.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.8.0.tgz#30e19ae3a27508ee18273ffed9ac7018949acd7b" + integrity sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw== + +tinypool@^0.8.3: + version "0.8.4" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8" + integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ== + +tinyspy@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" + integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + +tough-cookie@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== + dependencies: + punycode "^2.3.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tslib@^2.0.3: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@^4.0.0, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +typescript@^5.0.0, typescript@^5.2.2: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +ufo@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344" + integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2, uri-js@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +use-sync-external-store@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" + integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== + +vite-node@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f" + integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw== + dependencies: + cac "^6.7.14" + debug "^4.3.4" + pathe "^1.1.1" + picocolors "^1.0.0" + vite "^5.0.0" + +vite-plugin-svgr@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz#9f3bf5206b0ec510287e56d16f1915e729bb4e6b" + integrity sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA== + dependencies: + "@rollup/pluginutils" "^5.0.5" + "@svgr/core" "^8.1.0" + "@svgr/plugin-jsx" "^8.1.0" + +vite@^5.0.0: + version "5.2.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.11.tgz#726ec05555431735853417c3c0bfb36003ca0cbd" + integrity sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +vite@^5.2.0: + version "5.2.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.10.tgz#2ac927c91e99d51b376a5c73c0e4b059705f5bd7" + integrity sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f" + integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA== + dependencies: + "@vitest/expect" "1.6.0" + "@vitest/runner" "1.6.0" + "@vitest/snapshot" "1.6.0" + "@vitest/spy" "1.6.0" + "@vitest/utils" "1.6.0" + acorn-walk "^8.3.2" + chai "^4.3.10" + debug "^4.3.4" + execa "^8.0.1" + local-pkg "^0.5.0" + magic-string "^0.30.5" + pathe "^1.1.1" + picocolors "^1.0.0" + std-env "^3.5.0" + strip-literal "^2.0.0" + tinybench "^2.5.1" + tinypool "^0.8.3" + vite "^5.0.0" + vite-node "1.6.0" + why-is-node-running "^2.2.2" + +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-fetch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" + integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== + dependencies: + tr46 "^5.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + dependencies: + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + +which-collection@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +why-is-node-running@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.2.2.tgz#4185b2b4699117819e7154594271e7e344c9973e" + integrity sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.16.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" + integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== + +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.0.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + +yup@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.4.0.tgz#898dcd660f9fb97c41f181839d3d65c3ee15a43e" + integrity sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0"