├── .gitattributes
├── .github
├── FUNDING.yml
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── .gitlab-ci.yml
├── .gitlab
├── agents
│ └── k8s-cluster-1
│ │ └── config.yaml
└── merge_request_templates
│ └── General.md
├── .gitpod.yml
├── .whitesource
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── assets
├── database-lab-dark-mode.png
├── database-lab-dark-mode.svg
├── database-lab-light-mode.png
├── database-lab-light-mode.svg
├── db-lab.png
├── dle-demo-animated.gif
├── dle-logo-only-transparent-590x373.png
├── dle-logo-only-white-bg-640x640.png
├── dle-simple.png
├── dle-simple.svg
├── dle-square-1024x1024.png
├── dle-square-220x220.png
├── dle-square-512x512.png
├── dle.svg
├── dle_button.svg
└── star.gif
├── engine
├── .dockerignore
├── .gitlab-ci.yml
├── .golangci.yml
├── Dockerfile.ci-checker
├── Dockerfile.dblab-cli
├── Dockerfile.dblab-server
├── Dockerfile.dblab-server-debug
├── Dockerfile.dblab-server-zfs08
├── Dockerfile.swagger-ui
├── Makefile
├── api
│ ├── postman
│ │ ├── dblab.postman_collection.json
│ │ └── dblab.postman_environment.json
│ ├── swagger-spec
│ │ └── dblab_server_swagger.yaml
│ └── swagger-ui
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── index.css
│ │ ├── index.html
│ │ ├── oauth2-redirect.html
│ │ ├── swagger-initializer.js
│ │ ├── swagger-ui-bundle.js
│ │ ├── swagger-ui-bundle.js.map
│ │ ├── swagger-ui-es-bundle-core.js
│ │ ├── swagger-ui-es-bundle-core.js.map
│ │ ├── swagger-ui-es-bundle.js
│ │ ├── swagger-ui-es-bundle.js.map
│ │ ├── swagger-ui-standalone-preset.js
│ │ ├── swagger-ui-standalone-preset.js.map
│ │ ├── swagger-ui.css
│ │ ├── swagger-ui.css.map
│ │ ├── swagger-ui.js
│ │ └── swagger-ui.js.map
├── cmd
│ ├── cli
│ │ ├── commands
│ │ │ ├── client.go
│ │ │ ├── clone
│ │ │ │ ├── actions.go
│ │ │ │ └── command_list.go
│ │ │ ├── config
│ │ │ │ ├── actions.go
│ │ │ │ ├── command_list.go
│ │ │ │ ├── duration.go
│ │ │ │ ├── environment.go
│ │ │ │ └── file.go
│ │ │ ├── errors.go
│ │ │ ├── global
│ │ │ │ ├── actions.go
│ │ │ │ └── command_list.go
│ │ │ ├── instance
│ │ │ │ ├── actions.go
│ │ │ │ └── command_list.go
│ │ │ ├── port_forwarding.go
│ │ │ └── snapshot
│ │ │ │ ├── actions.go
│ │ │ │ └── command_list.go
│ │ ├── main.go
│ │ └── templates
│ │ │ └── help.go
│ ├── database-lab
│ │ └── main.go
│ └── runci
│ │ └── main.go
├── configs
│ ├── config.example.ci_checker.yml
│ ├── config.example.logical_generic.yml
│ ├── config.example.logical_rds_iam.yml
│ ├── config.example.physical_generic.yml
│ ├── config.example.physical_pgbackrest.yml
│ ├── config.example.physical_walg.yml
│ └── standard
│ │ └── postgres
│ │ ├── control
│ │ ├── pg_hba.conf
│ │ └── postgresql.conf
│ │ └── default
│ │ ├── 10
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ ├── 11
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ ├── 12
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ ├── 13
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ ├── 14
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ ├── 15
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ ├── 16
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ ├── 17
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
│ │ └── 9.6
│ │ ├── pg_hba.conf
│ │ └── postgresql.dblab.postgresql.conf
├── deploy
│ └── swagger-ui.yaml
├── go.mod
├── go.sum
├── internal
│ ├── billing
│ │ └── billing.go
│ ├── cloning
│ │ ├── base.go
│ │ ├── base_test.go
│ │ ├── snapshots.go
│ │ ├── snapshots_test.go
│ │ ├── storage.go
│ │ ├── storage_test.go
│ │ └── wrapper.go
│ ├── diagnostic
│ │ ├── logs.go
│ │ ├── logs_integration_test.go
│ │ └── logs_test.go
│ ├── embeddedui
│ │ ├── embedded_ui.go
│ │ ├── embedded_ui_integration_test.go
│ │ └── embedded_ui_test.go
│ ├── observer
│ │ ├── artifacts.go
│ │ ├── observer.go
│ │ ├── observer_test.go
│ │ ├── observing_clone.go
│ │ ├── queries.go
│ │ ├── session.go
│ │ ├── session_test.go
│ │ ├── sql.go
│ │ └── stats.go
│ ├── platform
│ │ ├── platform.go
│ │ └── platform_test.go
│ ├── portfwd
│ │ └── sshtunnel.go
│ ├── provision
│ │ ├── databases
│ │ │ └── postgres
│ │ │ │ ├── pgconfig
│ │ │ │ ├── configuration.go
│ │ │ │ └── configuration_test.go
│ │ │ │ ├── postgres.go
│ │ │ │ ├── postgres_mgmt.go
│ │ │ │ ├── postgres_mgmt_test.go
│ │ │ │ └── postgres_test.go
│ │ ├── docker
│ │ │ ├── docker.go
│ │ │ └── docker_test.go
│ │ ├── mode_local.go
│ │ ├── mode_local_test.go
│ │ ├── pool
│ │ │ ├── block_devices.go
│ │ │ ├── fstype_bsd.go
│ │ │ ├── fstype_linux.go
│ │ │ ├── fstype_windows.go
│ │ │ ├── manager.go
│ │ │ └── pool_manager.go
│ │ ├── port_checker.go
│ │ ├── provision.go
│ │ ├── resources
│ │ │ ├── appconfig.go
│ │ │ ├── pool.go
│ │ │ └── resources.go
│ │ ├── runners
│ │ │ └── runners.go
│ │ └── thinclones
│ │ │ ├── lvm
│ │ │ ├── lvm.go
│ │ │ └── lvmanager.go
│ │ │ ├── manager.go
│ │ │ └── zfs
│ │ │ ├── snapshots_filter.go
│ │ │ ├── snapshots_filter_test.go
│ │ │ ├── zfs.go
│ │ │ └── zfs_test.go
│ ├── retrieval
│ │ ├── components
│ │ │ └── components.go
│ │ ├── config
│ │ │ └── config.go
│ │ ├── dbmarker
│ │ │ └── dbmarker.go
│ │ ├── engine
│ │ │ ├── engine.go
│ │ │ └── postgres
│ │ │ │ ├── job_builder.go
│ │ │ │ ├── logical
│ │ │ │ ├── activity.go
│ │ │ │ ├── activity_test.go
│ │ │ │ ├── archive_types.go
│ │ │ │ ├── archive_types_test.go
│ │ │ │ ├── dump.go
│ │ │ │ ├── dump_default.go
│ │ │ │ ├── dump_integration_test.go
│ │ │ │ ├── dump_rds.go
│ │ │ │ ├── logical.go
│ │ │ │ ├── logical_test.go
│ │ │ │ ├── restore.go
│ │ │ │ └── restore_test.go
│ │ │ │ ├── physical
│ │ │ │ ├── custom.go
│ │ │ │ ├── custom_test.go
│ │ │ │ ├── pgbackrest.go
│ │ │ │ ├── pgbackrest_test.go
│ │ │ │ ├── physical.go
│ │ │ │ ├── physical_test.go
│ │ │ │ ├── wal_g.go
│ │ │ │ └── wal_g_test.go
│ │ │ │ ├── snapshot
│ │ │ │ ├── errors.go
│ │ │ │ ├── logical.go
│ │ │ │ ├── physical.go
│ │ │ │ ├── physical_integration_test.go
│ │ │ │ ├── physical_test.go
│ │ │ │ └── snapshot.go
│ │ │ │ └── tools
│ │ │ │ ├── activity
│ │ │ │ └── activity.go
│ │ │ │ ├── cont
│ │ │ │ ├── container.go
│ │ │ │ └── container_test.go
│ │ │ │ ├── db
│ │ │ │ ├── image_content.go
│ │ │ │ └── pg.go
│ │ │ │ ├── defaults
│ │ │ │ └── defaults.go
│ │ │ │ ├── dump.go
│ │ │ │ ├── fs
│ │ │ │ └── tools.go
│ │ │ │ ├── health
│ │ │ │ └── healthcheck.go
│ │ │ │ ├── pgtool
│ │ │ │ └── pgtool.go
│ │ │ │ ├── query
│ │ │ │ └── preprocessor.go
│ │ │ │ ├── tools.go
│ │ │ │ └── tools_test.go
│ │ ├── options
│ │ │ └── options.go
│ │ ├── refresh_errors.go
│ │ ├── retrieval.go
│ │ ├── retrieval_test.go
│ │ ├── state.go
│ │ ├── state_test.go
│ │ ├── status
│ │ │ └── retrieval_status.go
│ │ ├── validator.go
│ │ └── validator_test.go
│ ├── runci
│ │ ├── clone.go
│ │ ├── config.go
│ │ ├── handlers.go
│ │ ├── server.go
│ │ └── source
│ │ │ ├── github.go
│ │ │ └── source.go
│ ├── srv
│ │ ├── api
│ │ │ ├── errors.go
│ │ │ ├── errors_test.go
│ │ │ └── util.go
│ │ ├── billing.go
│ │ ├── config.go
│ │ ├── config
│ │ │ └── config.go
│ │ ├── config_test.go
│ │ ├── mw
│ │ │ ├── auth.go
│ │ │ ├── auth_test.go
│ │ │ └── logging.go
│ │ ├── routes.go
│ │ ├── server.go
│ │ ├── ws.go
│ │ ├── ws
│ │ │ ├── ping.go
│ │ │ ├── token.go
│ │ │ └── token_test.go
│ │ └── ws_test.go
│ ├── telemetry
│ │ ├── events.go
│ │ └── telemetry.go
│ └── validator
│ │ ├── validator.go
│ │ └── validator_test.go
├── pkg
│ ├── client
│ │ ├── dblabapi
│ │ │ ├── client.go
│ │ │ ├── client_test.go
│ │ │ ├── clone.go
│ │ │ ├── clone_test.go
│ │ │ ├── snapshot.go
│ │ │ ├── snapshot_test.go
│ │ │ ├── status.go
│ │ │ ├── status_test.go
│ │ │ └── types
│ │ │ │ ├── clone.go
│ │ │ │ └── observation.go
│ │ └── platform
│ │ │ ├── client.go
│ │ │ ├── client_test.go
│ │ │ ├── observation.go
│ │ │ ├── telemetry.go
│ │ │ └── token.go
│ ├── config
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── global
│ │ │ └── config.go
│ │ └── loaders.go
│ ├── log
│ │ ├── filtering.go
│ │ └── log.go
│ ├── models
│ │ ├── admin.go
│ │ ├── clone.go
│ │ ├── configuration.go
│ │ ├── database.go
│ │ ├── error.go
│ │ ├── fs.go
│ │ ├── instance_status.go
│ │ ├── local_time.go
│ │ ├── local_time_test.go
│ │ ├── observation.go
│ │ ├── retrieval.go
│ │ ├── retrieval_test.go
│ │ ├── snapshot.go
│ │ ├── status.go
│ │ └── sync.go
│ └── util
│ │ ├── backup
│ │ ├── backup.go
│ │ ├── backup_test.go
│ │ ├── collection.go
│ │ └── utils.go
│ │ ├── bytes.go
│ │ ├── clones.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── engine
│ │ └── engine.go
│ │ ├── networks
│ │ ├── networks.go
│ │ └── networks_test.go
│ │ ├── pglog
│ │ ├── activity.go
│ │ └── activity_test.go
│ │ ├── projection
│ │ ├── common_test.go
│ │ ├── helpers.go
│ │ ├── json.go
│ │ ├── load_json_test.go
│ │ ├── load_yaml_test.go
│ │ ├── multi_group_test.go
│ │ ├── operations.go
│ │ ├── store_json_test.go
│ │ ├── store_yaml_test.go
│ │ ├── tags.go
│ │ ├── types.go
│ │ └── yaml.go
│ │ ├── ptypes
│ │ └── mapping.go
│ │ ├── slices.go
│ │ ├── slices_test.go
│ │ ├── testing.go
│ │ ├── time.go
│ │ ├── time_test.go
│ │ └── yaml
│ │ ├── custom.go
│ │ ├── custom_test.go
│ │ ├── default.go
│ │ ├── mask.go
│ │ ├── mask_test.go
│ │ └── path.go
├── scripts
│ ├── adjust_configs.sh
│ ├── bash_autocomplete
│ ├── ci_docker_build_push.sh
│ ├── cleanup_zfs_snapshot.sh
│ ├── cli_install.sh
│ ├── create_zfs_snapshot.sh
│ ├── do.sh
│ └── preprocess_example.sh
├── test
│ ├── 1.synthetic.sh
│ ├── 2.logical_generic.sh
│ ├── 3.physical_walg.sh
│ ├── 4.physical_basebackup.sh
│ ├── 5.logical_rds.sh
│ ├── _cleanup.sh
│ ├── _prerequisites.ubuntu.sh
│ └── _zfs.file.sh
├── testdata
│ └── testdata.go
└── version
│ ├── version.go
│ └── version_test.go
├── translations
├── README.german.md
├── README.portuguese-br.md
├── README.russian.md
├── README.spanish.md
├── README.ukrainian.md
└── unfinished-review-needed
│ └── .gitkeep
└── ui
├── .dockerignore
├── .gitignore
├── .gitlab-ci.yml
├── .npmrc
├── .prettierrc
├── .stylelintrc
├── README.md
├── cspell.json
├── package.json
├── packages
├── ce
│ ├── .dockerignore
│ ├── .env
│ ├── .eslintrc
│ ├── .gitlab-ci.yml
│ ├── .npmrc
│ ├── Dockerfile
│ ├── ci_docker_build_push.sh
│ ├── craco.config.js
│ ├── cypress.config.ts
│ ├── cypress
│ │ └── e2e
│ │ │ └── tabs.cy.js
│ ├── docker-entrypoint.sh
│ ├── nginx.conf
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App
│ │ │ ├── Auth
│ │ │ │ ├── Card
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ │ ├── Instance
│ │ │ │ ├── Clones
│ │ │ │ │ ├── Clone
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── CreateClone
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── Page
│ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ │ ├── Menu
│ │ │ │ ├── Header
│ │ │ │ │ ├── icons
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── Instances
│ │ │ │ │ ├── icons
│ │ │ │ │ │ └── plus.svg
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── SignOutModal
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── StickyTopBar
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── styles.module.scss
│ │ │ │ │ └── utils
│ │ │ │ │ │ └── index.ts
│ │ │ │ ├── icons
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ │ └── index.tsx
│ │ ├── api
│ │ │ ├── clones
│ │ │ │ ├── createClone.ts
│ │ │ │ ├── destroyClone.ts
│ │ │ │ ├── getClone.ts
│ │ │ │ ├── resetClone.ts
│ │ │ │ └── updateClone.ts
│ │ │ ├── configs
│ │ │ │ ├── activateBilling.ts
│ │ │ │ ├── getBillingStatus.ts
│ │ │ │ ├── getConfig.ts
│ │ │ │ ├── getFullConfig.ts
│ │ │ │ ├── getSeImages.ts
│ │ │ │ ├── testDbSource.ts
│ │ │ │ └── updateConfig.ts
│ │ │ ├── engine
│ │ │ │ ├── getEngine.ts
│ │ │ │ ├── getWSToken.ts
│ │ │ │ └── initWS.ts
│ │ │ ├── instances
│ │ │ │ ├── getInstance.ts
│ │ │ │ └── getInstanceRetrieval.ts
│ │ │ └── snapshots
│ │ │ │ └── getSnapshots.ts
│ │ ├── components
│ │ │ ├── NavPath
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ │ └── PageContainer
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ ├── config
│ │ │ ├── env.ts
│ │ │ └── routes.tsx
│ │ ├── helpers
│ │ │ ├── edition.ts
│ │ │ ├── localStorage.ts
│ │ │ └── request.ts
│ │ ├── index.scss
│ │ ├── index.tsx
│ │ ├── react-app-env.d.ts
│ │ └── stores
│ │ │ └── app.ts
│ └── tsconfig.json
└── shared
│ ├── .gitlab-ci.yml
│ ├── .npmrc
│ ├── components
│ ├── AlertSnackbar
│ │ ├── index.tsx
│ │ └── useAlertSnackbar.tsx
│ ├── Button
│ │ └── index.tsx
│ ├── Button2
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── DestroyCloneModal
│ │ └── index.tsx
│ ├── DestroyCloneRestrictionModal
│ │ └── index.tsx
│ ├── ErrorStub
│ │ └── index.tsx
│ ├── FormattedText
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── GatewayLink
│ │ └── index.tsx
│ ├── HorizontalScrollContainer
│ │ ├── index.tsx
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── ImportantText
│ │ └── index.tsx
│ ├── Link2
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── MenuButton
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── Modal
│ │ └── index.tsx
│ ├── PageSpinner
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── ResetCloneModal
│ │ └── index.tsx
│ ├── SectionTitle
│ │ └── index.tsx
│ ├── Select
│ │ └── index.tsx
│ ├── SimpleModalControls
│ │ └── index.tsx
│ ├── Spinner
│ │ ├── icon.tsx
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── Status
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── StubContainer
│ │ └── index.tsx
│ ├── StubSpinner
│ │ └── index.tsx
│ ├── StubSpinnerFlex
│ │ ├── index.tsx
│ │ └── styles.module.scss
│ ├── SyntaxHighlight
│ │ └── index.tsx
│ ├── Table
│ │ ├── RowMenu
│ │ │ └── index.tsx
│ │ └── index.tsx
│ ├── Text
│ │ └── index.tsx
│ ├── TextField
│ │ └── index.tsx
│ └── Tooltip
│ │ └── index.tsx
│ ├── config
│ ├── index.ts
│ └── links.ts
│ ├── craco.config.js
│ ├── helpers
│ ├── getEntropy.ts
│ ├── localStorage.ts
│ └── request.ts
│ ├── hooks
│ └── useWindowDimensions.ts
│ ├── icons
│ ├── ArrowDropDown
│ │ └── index.tsx
│ ├── Circle
│ │ └── index.tsx
│ ├── External
│ │ └── index.tsx
│ ├── Info
│ │ └── index.tsx
│ ├── Renewable
│ │ └── index.tsx
│ ├── Shield
│ │ └── index.tsx
│ └── Warning
│ │ └── index.tsx
│ ├── meta.json
│ ├── package.json
│ ├── pages
│ ├── Clone
│ │ ├── Status
│ │ │ └── index.tsx
│ │ ├── context.ts
│ │ ├── index.tsx
│ │ ├── stores
│ │ │ └── Main.ts
│ │ └── useCreatedStores.ts
│ ├── Configuration
│ │ ├── Header
│ │ │ └── index.tsx
│ │ ├── InputWithTooltip
│ │ │ └── index.tsx
│ │ ├── ResponseMessage
│ │ │ └── index.tsx
│ │ ├── configOptions.ts
│ │ ├── index.tsx
│ │ ├── styles.module.scss
│ │ ├── tooltipText.tsx
│ │ ├── useForm.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── CreateClone
│ │ ├── index.tsx
│ │ ├── stores
│ │ │ └── Main.ts
│ │ ├── styles.module.scss
│ │ ├── useCreatedStores.ts
│ │ └── useForm.ts
│ ├── Instance
│ │ ├── Clones
│ │ │ ├── Header
│ │ │ │ ├── Item
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ │ └── index.tsx
│ │ ├── ClonesModal
│ │ │ ├── index.tsx
│ │ │ └── utils.ts
│ │ ├── InactiveInstance
│ │ │ ├── index.tsx
│ │ │ └── utils.ts
│ │ ├── Info
│ │ │ ├── Connection
│ │ │ │ ├── ConnectModal
│ │ │ │ │ ├── Content
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── utils.ts
│ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Details
│ │ │ │ └── index.tsx
│ │ │ ├── Disks
│ │ │ │ ├── Disk
│ │ │ │ │ ├── ActionsMenu
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── Marker
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── ProgressBar
│ │ │ │ │ │ ├── PointerIcon.tsx
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── Status
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Icons
│ │ │ │ └── index.tsx
│ │ │ ├── Retrieval
│ │ │ │ ├── RefreshFailedAlert
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── RetrievalModal
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── RetrievalTable
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── index.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── Snapshots
│ │ │ │ ├── Calendar
│ │ │ │ │ ├── Day
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── TimeLine
│ │ │ │ │ ├── Day
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── Status
│ │ │ │ ├── InstanceResponseModal
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── index.tsx
│ │ │ │ ├── styles.module.scss
│ │ │ │ └── utils.ts
│ │ │ ├── components
│ │ │ │ ├── Property
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.scss
│ │ │ │ ├── Section
│ │ │ │ │ └── index.tsx
│ │ │ │ └── ValueStatus
│ │ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── SnapshotsModal
│ │ │ ├── index.tsx
│ │ │ └── utils.ts
│ │ ├── Tabs
│ │ │ └── index.tsx
│ │ ├── components
│ │ │ ├── ClonesList
│ │ │ │ ├── ConnectionModal
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── MenuCell
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.scss
│ │ │ ├── ErrorStub
│ │ │ │ └── index.tsx
│ │ │ ├── ModalReloadButton
│ │ │ │ └── index.tsx
│ │ │ └── Tags
│ │ │ │ ├── Tag
│ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ ├── context.ts
│ │ ├── index.tsx
│ │ ├── stores
│ │ │ ├── ClonesModal.ts
│ │ │ ├── Main.ts
│ │ │ └── SnapshotsModal.ts
│ │ ├── styles.scss
│ │ └── useCreatedStores.ts
│ └── Logs
│ │ ├── Icons
│ │ └── PlusIcon.tsx
│ │ ├── constants
│ │ └── index.ts
│ │ ├── hooks
│ │ └── useWsScroll.tsx
│ │ ├── index.tsx
│ │ ├── utils
│ │ └── index.ts
│ │ ├── wsLogs.ts
│ │ └── wsSnackbar.ts
│ ├── react-app-env.d.ts
│ ├── scripts
│ ├── copy-assets.js
│ └── pack.js
│ ├── stores
│ └── Snapshots.ts
│ ├── styles
│ ├── colors.ts
│ ├── global.scss
│ ├── icons.tsx
│ ├── mixins.scss
│ ├── styles.ts
│ ├── theme.ts
│ ├── vars.scss
│ └── vars.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── types
│ ├── api
│ │ ├── endpoints
│ │ │ ├── createClone.ts
│ │ │ ├── destroyClone.ts
│ │ │ ├── getClone.ts
│ │ │ ├── getConfig.ts
│ │ │ ├── getEngine.ts
│ │ │ ├── getFullConfig.ts
│ │ │ ├── getInstance.ts
│ │ │ ├── getInstanceRetrieval.ts
│ │ │ ├── getSeImages.ts
│ │ │ ├── getSnapshots.ts
│ │ │ ├── getWSToken.ts
│ │ │ ├── initWS.ts
│ │ │ ├── refreshInstance.ts
│ │ │ ├── resetClone.ts
│ │ │ ├── testDbSource.ts
│ │ │ ├── updateClone.ts
│ │ │ └── updateConfig.ts
│ │ └── entities
│ │ │ ├── clone.ts
│ │ │ ├── config.ts
│ │ │ ├── dbSource.ts
│ │ │ ├── instance.ts
│ │ │ ├── instanceRetrieval.ts
│ │ │ ├── instanceState.ts
│ │ │ ├── pool.ts
│ │ │ ├── snapshot.ts
│ │ │ └── wsToken.ts
│ └── byte-size
│ │ └── index.d.ts
│ └── utils
│ ├── api.ts
│ ├── clone.ts
│ ├── connection.ts
│ ├── date.ts
│ ├── instance.ts
│ ├── numbers.ts
│ ├── react.ts
│ ├── snapshot.ts
│ ├── strings.ts
│ └── units.ts
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-detectable=false
2 | *.ts linguist-detectable=false
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: postgres-ai
4 | patreon: Database_Lab # Replace with a single Patreon username
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea/
3 |
4 | engine/bin/
5 | /db-lab-run/
6 |
7 | # Deploy contains Kubernetes configs with secrets, remove from .gitignore when generalized.
8 | /deploy/
9 |
10 | /engine/configs/config.yml
11 | /engine/configs/server.yml
12 | /engine/configs/run_ci.yaml
13 | /engine/configs/ci_checker.yml
14 |
15 | engine/meta
16 |
17 | ui/packages/shared/dist/
18 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | variables:
2 | DOCKER_DRIVER: overlay2
3 |
4 | workflow:
5 | rules:
6 | - if: $CI_PIPELINE_SOURCE == "merge_request_event"
7 | - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
8 | - if: $CI_COMMIT_TAG
9 | - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
10 | when: never
11 |
12 | include:
13 | - local: 'engine/.gitlab-ci.yml'
14 | - local: 'ui/.gitlab-ci.yml'
15 |
16 | empty-job:
17 | stage: test
18 | script:
19 | - echo 'success'
20 | rules:
21 | - if: $CI_PIPELINE_SOURCE == "merge_request_event"
22 | when: always
23 | - if: $CI_PIPELINE_SOURCE == "merge_request_event"
24 | changes:
25 | - engine/**/*
26 | - ui/**/*
27 | when: never
28 |
--------------------------------------------------------------------------------
/.gitlab/agents/k8s-cluster-1/config.yaml:
--------------------------------------------------------------------------------
1 | ci_access:
2 | projects:
3 | - id: postgres-ai/database-lab
4 |
--------------------------------------------------------------------------------
/.gitlab/merge_request_templates/General.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | [give description of a MR]
4 |
5 | # Related issue
6 |
7 | [give full link to the related issue]
8 |
9 | # Examples
10 |
11 | [present examples for reviewers: new CLI output, screenshots, etc]
12 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - before: >
3 | [[ ! -z $GNUGPG ]] &&
4 | cd ~ &&
5 | rm -rf .gnupg &&
6 | echo $GNUGPG | base64 -d | tar --no-same-owner -xzvf -
7 |
8 | # test
--------------------------------------------------------------------------------
/.whitesource:
--------------------------------------------------------------------------------
1 | {
2 | "scanSettings": {
3 | "baseBranches": []
4 | },
5 | "checkRunSettings": {
6 | "vulnerableCheckRunConclusionLevel": "failure",
7 | "displayMode": "diff"
8 | },
9 | "issueSettings": {
10 | "minSeverityLevel": "LOW"
11 | }
12 | }
--------------------------------------------------------------------------------
/assets/database-lab-dark-mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/database-lab-dark-mode.png
--------------------------------------------------------------------------------
/assets/database-lab-light-mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/database-lab-light-mode.png
--------------------------------------------------------------------------------
/assets/db-lab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/db-lab.png
--------------------------------------------------------------------------------
/assets/dle-demo-animated.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/dle-demo-animated.gif
--------------------------------------------------------------------------------
/assets/dle-logo-only-transparent-590x373.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/dle-logo-only-transparent-590x373.png
--------------------------------------------------------------------------------
/assets/dle-logo-only-white-bg-640x640.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/dle-logo-only-white-bg-640x640.png
--------------------------------------------------------------------------------
/assets/dle-simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/dle-simple.png
--------------------------------------------------------------------------------
/assets/dle-square-1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/dle-square-1024x1024.png
--------------------------------------------------------------------------------
/assets/dle-square-220x220.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/dle-square-220x220.png
--------------------------------------------------------------------------------
/assets/dle-square-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/dle-square-512x512.png
--------------------------------------------------------------------------------
/assets/star.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/assets/star.gif
--------------------------------------------------------------------------------
/engine/.dockerignore:
--------------------------------------------------------------------------------
1 | meta/
--------------------------------------------------------------------------------
/engine/Dockerfile.ci-checker:
--------------------------------------------------------------------------------
1 | FROM docker:20.10.24
2 |
3 | # Install dependencies.
4 | RUN apk update && apk add --no-cache bash
5 |
6 | WORKDIR /home/dblab
7 |
8 | COPY ./bin/run-ci ./bin/run-ci
9 |
10 | CMD ./bin/run-ci
11 |
--------------------------------------------------------------------------------
/engine/Dockerfile.dblab-cli:
--------------------------------------------------------------------------------
1 | FROM docker:20.10.24
2 |
3 | # Install dependencies.
4 | RUN apk update && apk add --no-cache bash jq tzdata
5 |
6 | WORKDIR /home/dblab
7 | COPY ./bin/dblab ./bin/dblab
8 | CMD ./bin/dblab
9 |
--------------------------------------------------------------------------------
/engine/Dockerfile.dblab-server:
--------------------------------------------------------------------------------
1 | # See Guides to learn how to start a container: https://postgres.ai/docs/how-to-guides/administration/engine-manage
2 |
3 | FROM docker:20.10.24
4 |
5 | # Install dependencies
6 | RUN apk update \
7 | && apk add --no-cache zfs lvm2 bash util-linux tzdata
8 |
9 | WORKDIR /home/dblab
10 |
11 | COPY ./bin/dblab-server ./bin/dblab-server
12 | COPY ./configs/standard ./standard
13 | COPY ./api ./api
14 | COPY ./scripts ./scripts
15 |
16 | CMD ./bin/dblab-server
17 |
--------------------------------------------------------------------------------
/engine/Dockerfile.dblab-server-debug:
--------------------------------------------------------------------------------
1 | # How to start a container: https://postgres.ai/docs/how-to-guides/administration/engine-manage
2 |
3 | # Compile stage
4 | FROM golang:1.23 AS build-env
5 |
6 | # Build Delve
7 | RUN go install github.com/go-delve/delve/cmd/dlv@latest
8 |
9 | # Build DLE (Uncomment if the binary doesn't compile locally).
10 | # ADD . /dockerdev
11 | # WORKDIR /dockerdev
12 | # RUN GO111MODULE=on CGO_ENABLED=0 go build -gcflags="all=-N -l" -o /dblab-server-debug ./cmd/database-lab/main.go
13 |
14 | # Final stage
15 | FROM docker:20.10.12
16 |
17 | # Install dependencies
18 | RUN apk update \
19 | && apk add zfs=2.1.4-r0 --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main \
20 | && apk add --no-cache lvm2 bash util-linux tzdata
21 |
22 | WORKDIR /home/dblab
23 |
24 | EXPOSE 2345 40000
25 |
26 | # Replace if the binary doesn't compile locally.
27 | # COPY --from=build-env /dblab-server-debug ./bin/dblab-server-debug
28 | COPY ./bin/dblab-server-debug ./bin/dblab-server-debug
29 |
30 | COPY --from=build-env /go/bin/dlv ./
31 | COPY ./configs/standard ./standard
32 | COPY ./api ./api
33 | COPY ./scripts ./scripts
34 |
35 | CMD ["./dlv", "--listen=:40000", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "./bin/dblab-server-debug"]
36 |
--------------------------------------------------------------------------------
/engine/Dockerfile.dblab-server-zfs08:
--------------------------------------------------------------------------------
1 | # See Guides to learn how to start a container: https://postgres.ai/docs/how-to-guides/administration/engine-manage
2 |
3 | FROM docker:20.10.24
4 |
5 | # Install dependencies.
6 | RUN apk update && apk add zfs=0.8.4-r0 --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.12/main \
7 | && apk add --no-cache lvm2 bash util-linux tzdata
8 |
9 | WORKDIR /home/dblab
10 |
11 | COPY ./bin/dblab-server ./bin/dblab-server
12 | COPY ./configs/standard ./standard
13 | COPY ./api ./api
14 | COPY ./scripts ./scripts
15 |
16 | CMD ./bin/dblab-server
17 |
--------------------------------------------------------------------------------
/engine/Dockerfile.swagger-ui:
--------------------------------------------------------------------------------
1 | FROM nginx:1.17-alpine
2 | COPY ./api/swagger-spec/ /usr/share/nginx/html/api/swagger-spec/
3 | COPY ./api/swagger-ui /usr/share/nginx/html
4 |
--------------------------------------------------------------------------------
/engine/api/postman/dblab.postman_environment.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "ff4200f0-7acd-eb4f-1dee-59da8c98c313",
3 | "name": "Database Lab",
4 | "values": [
5 | {
6 | "enabled": true,
7 | "key": "DBLAB_URL",
8 | "value": "https://url",
9 | "type": "text"
10 | },
11 | {
12 | "enabled": true,
13 | "key": "DBLAB_VERIFY_TOKEN",
14 | "value": "secret_token",
15 | "type": "text"
16 | }
17 | ],
18 | "timestamp": 1580454458304,
19 | "_postman_variable_scope": "environment",
20 | "_postman_exported_at": "2020-01-31T09:42:37.377Z",
21 | "_postman_exported_using": "Postman/5.5.4"
22 | }
23 |
--------------------------------------------------------------------------------
/engine/api/swagger-ui/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/engine/api/swagger-ui/favicon-16x16.png
--------------------------------------------------------------------------------
/engine/api/swagger-ui/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/engine/api/swagger-ui/favicon-32x32.png
--------------------------------------------------------------------------------
/engine/api/swagger-ui/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | overflow: -moz-scrollbars-vertical;
4 | overflow-y: scroll;
5 | }
6 |
7 | *,
8 | *:before,
9 | *:after {
10 | box-sizing: inherit;
11 | }
12 |
13 | body {
14 | margin: 0;
15 | background: #fafafa;
16 | }
17 |
--------------------------------------------------------------------------------
/engine/api/swagger-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Swagger UI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/engine/api/swagger-ui/swagger-initializer.js:
--------------------------------------------------------------------------------
1 | window.onload = function() {
2 | //
3 |
4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container
5 | window.ui = SwaggerUIBundle({
6 | url: "api/swagger-spec/dblab_server_swagger.yaml",
7 | dom_id: '#swagger-ui',
8 | deepLinking: true,
9 | presets: [
10 | SwaggerUIBundle.presets.apis,
11 | SwaggerUIStandalonePreset
12 | ],
13 | plugins: [
14 | SwaggerUIBundle.plugins.DownloadUrl
15 | ],
16 | layout: "StandaloneLayout"
17 | });
18 |
19 | //
20 | };
21 |
--------------------------------------------------------------------------------
/engine/cmd/cli/commands/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package commands
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | )
11 |
12 | // ActionError defines a custom type of CLI action error.
13 | type ActionError struct {
14 | err error
15 | }
16 |
17 | // NewActionError constructs a new Action error.
18 | func NewActionError(msg string) ActionError {
19 | return ActionError{err: errors.New(msg)}
20 | }
21 |
22 | // ToActionError wraps the error to a new action error.
23 | func ToActionError(err error) error {
24 | if err == nil {
25 | return nil
26 | }
27 |
28 | return ActionError{err: err}
29 | }
30 |
31 | // ActionErrorf formats according to a format specifier.
32 | func ActionErrorf(format string, args ...interface{}) error {
33 | return ActionError{
34 | err: fmt.Errorf(format, args...),
35 | }
36 | }
37 |
38 | // Error returns an output of the action error.
39 | func (e ActionError) Error() string {
40 | return "[ERROR]: " + e.err.Error()
41 | }
42 |
--------------------------------------------------------------------------------
/engine/cmd/cli/commands/instance/command_list.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package instance
6 |
7 | import (
8 | "github.com/urfave/cli/v2"
9 | )
10 |
11 | // CommandList returns available commands for an instance management.
12 | func CommandList() []*cli.Command {
13 | return []*cli.Command{
14 | {
15 | Name: "instance",
16 | Usage: "displays instance info",
17 | Subcommands: []*cli.Command{
18 | {
19 | Name: "status",
20 | Usage: "display instance's status",
21 | Action: status,
22 | },
23 | {
24 | Name: "version",
25 | Usage: "display instance's version",
26 | Action: health,
27 | },
28 | },
29 | },
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/engine/cmd/cli/commands/snapshot/actions.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package snapshot provides snapshot management commands.
6 | package snapshot
7 |
8 | import (
9 | "encoding/json"
10 | "fmt"
11 |
12 | "github.com/urfave/cli/v2"
13 |
14 | "gitlab.com/postgres-ai/database-lab/v3/cmd/cli/commands"
15 | "gitlab.com/postgres-ai/database-lab/v3/pkg/models"
16 | )
17 |
18 | // list runs a request to list snapshots of an instance.
19 | func list(cliCtx *cli.Context) error {
20 | dblabClient, err := commands.ClientByCLIContext(cliCtx)
21 | if err != nil {
22 | return err
23 | }
24 |
25 | body, err := dblabClient.ListSnapshotsRaw(cliCtx.Context)
26 | if err != nil {
27 | return err
28 | }
29 |
30 | defer func() { _ = body.Close() }()
31 |
32 | var snapshotListView []*models.SnapshotView
33 |
34 | if err := json.NewDecoder(body).Decode(&snapshotListView); err != nil {
35 | return err
36 | }
37 |
38 | commandResponse, err := json.MarshalIndent(snapshotListView, "", " ")
39 | if err != nil {
40 | return err
41 | }
42 |
43 | _, err = fmt.Fprintln(cliCtx.App.Writer, string(commandResponse))
44 |
45 | return err
46 | }
47 |
--------------------------------------------------------------------------------
/engine/cmd/cli/commands/snapshot/command_list.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package snapshot
6 |
7 | import (
8 | "github.com/urfave/cli/v2"
9 | )
10 |
11 | // CommandList returns available commands for a snapshot management.
12 | func CommandList() []*cli.Command {
13 | return []*cli.Command{
14 | {
15 | Name: "snapshot",
16 | Usage: "manage snapshots",
17 | Subcommands: []*cli.Command{
18 | {
19 | Name: "list",
20 | Usage: "list all existing snapshots",
21 | Action: list,
22 | },
23 | },
24 | },
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/engine/configs/standard/postgres/control/pg_hba.conf:
--------------------------------------------------------------------------------
1 | ## This file will replace any pg_hba.conf located in your PGDATA.
2 |
3 | local all all trust
4 | host all all 0.0.0.0/0 md5
5 |
--------------------------------------------------------------------------------
/engine/configs/standard/postgres/default/11/pg_hba.conf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/engine/configs/standard/postgres/default/11/pg_hba.conf
--------------------------------------------------------------------------------
/engine/deploy/swagger-ui.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: dblab-swagger-ui
5 | labels:
6 | app: dblab-swagger-ui
7 | spec:
8 | type: ClusterIP
9 | ports:
10 | - port: 80
11 | targetPort: 80
12 | selector:
13 | app: dblab-swagger-ui
14 | ---
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name: dblab-swagger-ui
19 | labels:
20 | app: dblab-swagger-ui
21 | spec:
22 | replicas: 1
23 | selector:
24 | matchLabels:
25 | app: dblab-swagger-ui
26 | template:
27 | metadata:
28 | labels:
29 | app: dblab-swagger-ui
30 | spec:
31 | containers:
32 | - name: dblab-swagger-ui
33 | image: $TAG
34 | imagePullPolicy: Always
35 |
--------------------------------------------------------------------------------
/engine/internal/cloning/wrapper.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | // Package cloning provides a cloning service.
6 | package cloning
7 |
8 | import (
9 | "time"
10 |
11 | "gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
12 |
13 | "gitlab.com/postgres-ai/database-lab/v3/pkg/models"
14 | )
15 |
16 | // CloneWrapper represents a cloning service wrapper.
17 | type CloneWrapper struct {
18 | Clone *models.Clone `json:"clone"`
19 | Session *resources.Session `json:"session"`
20 |
21 | TimeCreatedAt time.Time `json:"time_created_at"`
22 | TimeStartedAt time.Time `json:"time_started_at"`
23 | }
24 |
25 | // NewCloneWrapper constructs a new CloneWrapper.
26 | func NewCloneWrapper(clone *models.Clone, createdAt time.Time) *CloneWrapper {
27 | w := &CloneWrapper{
28 | Clone: clone,
29 | TimeCreatedAt: createdAt,
30 | }
31 |
32 | return w
33 | }
34 |
35 | // IsProtected checks if clone is protected.
36 | func (cw CloneWrapper) IsProtected() bool {
37 | return cw.Clone != nil && cw.Clone.Protected
38 | }
39 |
--------------------------------------------------------------------------------
/engine/internal/diagnostic/logs_test.go:
--------------------------------------------------------------------------------
1 | package diagnostic
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 | "time"
8 |
9 | "github.com/google/uuid"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | func TestLogsCleanup(t *testing.T) {
16 | t.Parallel()
17 | now := time.Now()
18 |
19 | dir := t.TempDir()
20 |
21 | for day := 0; day <= 10; day++ {
22 | containerName, err := uuid.NewUUID()
23 | assert.NoError(t, err)
24 |
25 | name := now.AddDate(0, 0, -1*day).In(time.UTC).Format(timeFormat)
26 |
27 | err = os.MkdirAll(path.Join(dir, name, containerName.String()), 0755)
28 | require.NoError(t, err)
29 | }
30 |
31 | err := cleanupLogsDir(dir, 5)
32 | require.NoError(t, err)
33 |
34 | // list remaining directories
35 | dirList, err := os.ReadDir(dir)
36 | assert.NoError(t, err)
37 | assert.Equal(t, 5, len(dirList))
38 | }
39 |
--------------------------------------------------------------------------------
/engine/internal/embeddedui/embedded_ui_test.go:
--------------------------------------------------------------------------------
1 | package embeddedui
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestUIManagerOriginURL(t *testing.T) {
10 | testCases := []struct {
11 | host string
12 | port int
13 | result string
14 | }{
15 | {
16 | host: "example.com",
17 | port: 2345,
18 | result: "http://example.com:2345",
19 | },
20 | {
21 | host: "example.com",
22 | result: "http://example.com:0",
23 | },
24 | {
25 | host: "container_host",
26 | port: 2345,
27 | result: "http://container_host:2345",
28 | },
29 | {
30 | host: "172.168.1.1",
31 | port: 2345,
32 | result: "http://172.168.1.1:2345",
33 | },
34 | {
35 | host: "0.0.0.0",
36 | port: 2345,
37 | result: "http://127.0.0.1:2345",
38 | },
39 | {
40 | host: "",
41 | port: 2345,
42 | result: "http://127.0.0.1:2345",
43 | },
44 | }
45 |
46 | for _, tc := range testCases {
47 | uiManager := UIManager{
48 | cfg: Config{
49 | Enabled: true,
50 | Host: tc.host,
51 | Port: tc.port,
52 | },
53 | }
54 |
55 | require.True(t, uiManager.IsEnabled())
56 | require.Equal(t, tc.result, uiManager.OriginURL())
57 |
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/engine/internal/observer/queries.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package observer
6 |
7 | import (
8 | "fmt"
9 | )
10 |
11 | const queryLocks = `with lock_data as (
12 | select
13 | a.datname,
14 | l.relation::regclass,
15 | l.transactionid,
16 | l.mode,
17 | l.locktype,
18 | l.granted,
19 | a.usename,
20 | a.query,
21 | a.query_start,
22 | a.state,
23 | a.wait_event_type,
24 | a.wait_event,
25 | a.xact_start,
26 | clock_timestamp() - a.xact_start as xact_duration,
27 | a.query_start,
28 | clock_timestamp() - a.query_start as query_duration,
29 | a.state_change,
30 | clock_timestamp() - a.state_change as state_changed_ago,
31 | a.pid
32 | from pg_stat_activity a
33 | join pg_locks l on l.pid = a.pid
34 | where l.mode = 'AccessExclusiveLock' and l.locktype = 'relation' and a.application_name <> '%s'
35 | )
36 | select row_to_json(lock_data)
37 | from lock_data
38 | where query_duration > interval '%d second'
39 | order by query_duration desc;`
40 |
41 | func buildLocksMetricQuery(exclusionApplicationName string, maxLockDurationSeconds uint64) string {
42 | return fmt.Sprintf(queryLocks, exclusionApplicationName, maxLockDurationSeconds)
43 | }
44 |
--------------------------------------------------------------------------------
/engine/internal/observer/session_test.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestSessionIsFinished(t *testing.T) {
11 | s := Session{}
12 | assert.False(t, s.IsFinished())
13 |
14 | s.FinishedAt = time.Now()
15 | assert.True(t, s.IsFinished())
16 | }
17 |
--------------------------------------------------------------------------------
/engine/internal/platform/platform_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package platform
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestIfPersonalTokenEnabled(t *testing.T) {
14 | s := Service{}
15 | assert.Equal(t, s.IsPersonalTokenEnabled(), false)
16 |
17 | s.cfg.EnablePersonalToken = true
18 | assert.Equal(t, s.IsPersonalTokenEnabled(), true)
19 | }
20 |
21 | func TestIfOrganizationIsAllowed(t *testing.T) {
22 | s := Service{}
23 | assert.Equal(t, s.isAllowedOrganization(0), false)
24 |
25 | s.token.OrganizationID = 1
26 | assert.Equal(t, s.isAllowedOrganization(0), false)
27 | assert.Equal(t, s.isAllowedOrganization(1), true)
28 | }
29 |
30 | func TestOriginURL(t *testing.T) {
31 | s := Service{
32 | cfg: Config{
33 | URL: "https://example.com:2345/api/path",
34 | },
35 | }
36 |
37 | assert.Equal(t, "https://example.com:2345", s.OriginURL())
38 | }
39 |
--------------------------------------------------------------------------------
/engine/internal/provision/databases/postgres/pgconfig/configuration_test.go:
--------------------------------------------------------------------------------
1 | package pgconfig
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestReadConfig(t *testing.T) {
12 | controlData := `
13 | restore_command = 'wal-g wal-fetch %f %p'
14 | standby_mode = 'on'
15 | recovery_target_timeline = latest
16 | `
17 | expected := map[string]string{
18 | "restore_command": "wal-g wal-fetch %f %p",
19 | "standby_mode": "on",
20 | "recovery_target_timeline": "latest",
21 | }
22 |
23 | f, err := os.CreateTemp("", "readPGConfig*")
24 | require.Nil(t, err)
25 | defer func() { _ = os.Remove(f.Name()) }()
26 |
27 | err = os.WriteFile(f.Name(), []byte(controlData), 0644)
28 | require.Nil(t, err)
29 |
30 | fileConfig, err := readConfig(f.Name())
31 | require.Nil(t, err)
32 |
33 | assert.Equal(t, len(expected), len(fileConfig))
34 | assert.Equal(t, expected["restore_command"], fileConfig["restore_command"])
35 | assert.Equal(t, expected["standby_mode"], fileConfig["standby_mode"])
36 | assert.Equal(t, expected["recovery_target_timeline"], fileConfig["recovery_target_timeline"])
37 | }
38 |
--------------------------------------------------------------------------------
/engine/internal/provision/pool/fstype_bsd.go:
--------------------------------------------------------------------------------
1 | //go:build darwin || freebsd || dragonfly || openbsd || solaris
2 | // +build darwin freebsd dragonfly openbsd solaris
3 |
4 | /*
5 | 2020 © Postgres.ai
6 | */
7 |
8 | // Package pool provides components to work with storage pools.
9 | package pool
10 |
11 | import (
12 | "syscall"
13 |
14 | "gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones/lvm"
15 | )
16 |
17 | func (pm *Manager) getFSInfo(path string) (string, error) {
18 | fs := syscall.Statfs_t{}
19 | if err := syscall.Statfs(path, &fs); err != nil {
20 | return "", err
21 | }
22 |
23 | fsType := detectFSType(fs.Fstypename[:])
24 | if fsType == ext4 {
25 | // cannot detect LVM checking the blockDeviceTypes map.
26 | return lvm.PoolMode, nil
27 | }
28 |
29 | return fsType, nil
30 | }
31 |
32 | // detectFSType detects the filesystem type of the underlying mounted filesystem.
33 | func detectFSType(fsType []int8) string {
34 | fsTypeBytes := make([]byte, 0, len(fsType))
35 |
36 | for _, v := range fsType {
37 | fsTypeBytes = append(fsTypeBytes, byte(v))
38 | }
39 |
40 | return string(fsTypeBytes)
41 | }
42 |
--------------------------------------------------------------------------------
/engine/internal/provision/pool/fstype_linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux && !s390x && !arm && !386
2 | // +build linux,!s390x,!arm,!386
3 |
4 | /*
5 | 2020 © Postgres.ai
6 | */
7 |
8 | // Package pool provides components to work with storage pools.
9 | package pool
10 |
11 | import (
12 | "strconv"
13 | "syscall"
14 |
15 | "gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones/lvm"
16 | "gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones/zfs"
17 | )
18 |
19 | var fsTypeToString = map[string]string{
20 | "ef53": ext4,
21 | "2fc12fc1": zfs.PoolMode,
22 | }
23 |
24 | func (pm *Manager) getFSInfo(path string) (string, error) {
25 | fs := syscall.Statfs_t{}
26 | if err := syscall.Statfs(path, &fs); err != nil {
27 | return "", err
28 | }
29 |
30 | fsType := detectFSType(fs.Type)
31 | if fsType == ext4 {
32 | // cannot detect LVM checking the blockDeviceTypes map.
33 | return lvm.PoolMode, nil
34 | }
35 |
36 | return fsType, nil
37 | }
38 |
39 | // detectFSType detects the filesystem type of the underlying mounted filesystem.
40 | func detectFSType(fsType int64) string {
41 | return fsTypeToString[strconv.FormatInt(fsType, 16)]
42 | }
43 |
--------------------------------------------------------------------------------
/engine/internal/provision/pool/fstype_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | /*
5 | 2020 © Postgres.ai
6 | */
7 |
8 | // Package pool provides components to work with storage pools.
9 | package pool
10 |
11 | func (pm *Manager) getFSInfo(path string) (string, error) {
12 | // Not supported for windows.
13 | return "", nil
14 | }
15 |
--------------------------------------------------------------------------------
/engine/internal/provision/port_checker.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package provision
6 |
7 | import (
8 | "net"
9 | "strconv"
10 |
11 | "github.com/pkg/errors"
12 | )
13 |
14 | type portChecker interface {
15 | checkPortAvailability(host string, port uint) error
16 | }
17 |
18 | type localPortChecker struct{}
19 |
20 | func (c *localPortChecker) checkPortAvailability(host string, port uint) error {
21 | addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
22 |
23 | conn, err := net.DialTimeout("tcp", addr, portCheckingTimeout)
24 | if conn != nil {
25 | _ = conn.Close()
26 | }
27 |
28 | if err == nil {
29 | return errors.New("port already in use")
30 | }
31 |
32 | if opErr, ok := err.(*net.OpError); ok &&
33 | opErr != nil && opErr.Err != nil && opErr.Err.Error() == "connect: connection refused" {
34 | return nil
35 | }
36 |
37 | return err
38 | }
39 |
--------------------------------------------------------------------------------
/engine/internal/provision/provision.go:
--------------------------------------------------------------------------------
1 | /*
2 | Provision wrapper
3 |
4 | 2019-2020 © Postgres.ai
5 | */
6 |
7 | // Package provision provides an interface to provision Database Lab clones.
8 | package provision
9 |
10 | // NoRoomError defines a specific error type.
11 | type NoRoomError struct {
12 | msg string
13 | }
14 |
15 | // NewNoRoomError instances a new NoRoomError.
16 | func NewNoRoomError(errorMessage string) error {
17 | return &NoRoomError{msg: errorMessage}
18 | }
19 |
20 | func (e *NoRoomError) Error() string {
21 | // TODO(anatoly): Change message.
22 | return "session cannot be started because there is no room: " + e.msg
23 | }
24 |
--------------------------------------------------------------------------------
/engine/internal/provision/thinclones/manager.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package thinclones provides an interface to work different thin-clone managers.
6 | package thinclones
7 |
8 | import (
9 | "fmt"
10 | )
11 |
12 | // SnapshotExistsError defines an error when snapshot already exists.
13 | type SnapshotExistsError struct {
14 | name string
15 | }
16 |
17 | // NewSnapshotExistsError creates a new SnapshotExistsError.
18 | func NewSnapshotExistsError(name string) *SnapshotExistsError {
19 | return &SnapshotExistsError{name: name}
20 | }
21 |
22 | // Error prints a message describing SnapshotExistsError.
23 | func (e *SnapshotExistsError) Error() string {
24 | return fmt.Sprintf(`snapshot %s already exists`, e.name)
25 | }
26 |
--------------------------------------------------------------------------------
/engine/internal/provision/thinclones/zfs/snapshots_filter.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | package zfs
6 |
7 | import (
8 | "strings"
9 | )
10 |
11 | type dsType string
12 |
13 | const (
14 | snapshotType dsType = "snapshot"
15 | fileSystemType dsType = "filesystem"
16 | )
17 |
18 | type snapshotFields []string
19 |
20 | type snapshotSorting []string
21 |
22 | type snapshotFilter struct {
23 | fields snapshotFields
24 | sorting snapshotSorting
25 | dsType dsType
26 | pool string
27 | }
28 |
29 | var defaultFields = snapshotFields{
30 | "name",
31 | "used",
32 | "mountpoint",
33 | "compressratio",
34 | "available",
35 | "type",
36 | "origin",
37 | "creation",
38 | "referenced",
39 | "logicalreferenced",
40 | "logicalused",
41 | "usedbysnapshots",
42 | "usedbychildren",
43 | dataStateAtLabel,
44 | }
45 |
46 | var defaultSorting = snapshotSorting{
47 | "-S " + dataStateAtLabel,
48 | "-S creation",
49 | }
50 |
51 | func buildListCommand(filter snapshotFilter) string {
52 | cmdComponents := []string{
53 | "zfs list",
54 | "-po", strings.Join(filter.fields, ","),
55 | strings.Join(filter.sorting, " "),
56 | "-t", string(filter.dsType),
57 | }
58 |
59 | if filter.pool != "" {
60 | cmdComponents = append(cmdComponents, "-r", filter.pool)
61 | }
62 |
63 | return strings.Join(cmdComponents, " ")
64 | }
65 |
--------------------------------------------------------------------------------
/engine/internal/provision/thinclones/zfs/snapshots_filter_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | package zfs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestListCommand(t *testing.T) {
14 | testCases := []struct {
15 | filter snapshotFilter
16 | expectedCommand string
17 | }{
18 | {
19 | filter: snapshotFilter{
20 | fields: []string{"name", "creation"},
21 | sorting: []string{"-S creation"},
22 | dsType: "snapshot",
23 | pool: "test_pool",
24 | },
25 | expectedCommand: "zfs list -po name,creation -S creation -t snapshot -r test_pool",
26 | },
27 | {
28 | filter: snapshotFilter{
29 | fields: []string{"name", "creation", "dblab:datastateat"},
30 | sorting: []string{"-S creation", "-S dblab:datastateat"},
31 | dsType: "filesystem",
32 | },
33 | expectedCommand: "zfs list -po name,creation,dblab:datastateat -S creation -S dblab:datastateat -t filesystem",
34 | },
35 | }
36 |
37 | for _, tc := range testCases {
38 | listCmd := buildListCommand(tc.filter)
39 | assert.Equal(t, tc.expectedCommand, listCmd)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/components/components.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package components provides the key components of data retrieval.
6 | package components
7 |
8 | import (
9 | "context"
10 |
11 | "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/config"
12 | "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools/activity"
13 | )
14 |
15 | // JobBuilder builds jobs.
16 | type JobBuilder interface {
17 | // BuildJob builds retrieval jobs.
18 | BuildJob(jobConfig config.JobConfig) (JobRunner, error)
19 | }
20 |
21 | // JobRunner performs a job.
22 | type JobRunner interface {
23 | // Name returns a job name.
24 | Name() string
25 |
26 | // Reload reloads job configuration.
27 | Reload(cfg map[string]interface{}) error
28 |
29 | // Run starts a job.
30 | Run(ctx context.Context) error
31 |
32 | // ReportActivity reports the current job activity.
33 | ReportActivity(context.Context) (*activity.Activity, error)
34 | }
35 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/config/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package config contains configuration options of the data retrieval.
6 | package config
7 |
8 | import (
9 | "github.com/docker/docker/client"
10 |
11 | "gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
12 | "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/dbmarker"
13 | )
14 |
15 | // Config describes of data retrieval jobs.
16 | type Config struct {
17 | Refresh *Refresh `yaml:"refresh"`
18 | Jobs []string `yaml:"jobs,flow"`
19 | JobsSpec map[string]JobSpec `yaml:"spec"`
20 | }
21 |
22 | // Refresh describes full-refresh options.
23 | type Refresh struct {
24 | Timetable string `yaml:"timetable"`
25 | SkipStartRefresh bool `yaml:"skipStartRefresh"`
26 | }
27 |
28 | // JobSpec contains details about a job.
29 | type JobSpec struct {
30 | Name string `yaml:"name"`
31 | Options map[string]interface{} `yaml:"options"`
32 | }
33 |
34 | // JobConfig describes a job configuration.
35 | type JobConfig struct {
36 | Spec JobSpec
37 | Docker *client.Client
38 | Marker *dbmarker.Marker
39 | FSPool *resources.Pool
40 | }
41 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/engine.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package engine provides different engines.
6 | package engine
7 |
8 | import (
9 | "errors"
10 |
11 | "gitlab.com/postgres-ai/database-lab/v3/internal/provision/pool"
12 | "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/components"
13 | "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres"
14 | "gitlab.com/postgres-ai/database-lab/v3/internal/telemetry"
15 |
16 | "gitlab.com/postgres-ai/database-lab/v3/pkg/config/global"
17 | )
18 |
19 | // JobBuilder provides a new job builder.
20 | func JobBuilder(globalCfg *global.Config, engineProps *global.EngineProps, cloneManager pool.FSManager,
21 | tm *telemetry.Agent) (components.JobBuilder, error) {
22 | switch globalCfg.Engine {
23 | case postgres.EngineType:
24 | return postgres.NewJobBuilder(globalCfg, engineProps, cloneManager, tm), nil
25 |
26 | default:
27 | return nil, errors.New("failed to get engine")
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/logical/archive_types.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | package logical
6 |
7 | import (
8 | "path/filepath"
9 | )
10 |
11 | type compressionType string
12 |
13 | const (
14 | noCompression compressionType = "no"
15 | gzipCompression compressionType = "gzip"
16 | bzip2Compression compressionType = "bzip2"
17 | )
18 |
19 | // getReadingArchiveCommand chooses command to read dump file.
20 | func getReadingArchiveCommand(compressionType compressionType) string {
21 | switch compressionType {
22 | case gzipCompression:
23 | return "gunzip -c"
24 |
25 | case bzip2Compression:
26 | return "bunzip2 -c"
27 |
28 | default:
29 | return "cat"
30 | }
31 | }
32 |
33 | // getCompressionType returns archive type based on filename extension.
34 | func getCompressionType(filename string) compressionType {
35 | switch filepath.Ext(filename) {
36 | case ".gz":
37 | return gzipCompression
38 |
39 | case ".bz2":
40 | return bzip2Compression
41 |
42 | default:
43 | return noCompression
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/logical/dump_default.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package logical
6 |
7 | import (
8 | "context"
9 |
10 | "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/engine/postgres/tools/db"
11 | )
12 |
13 | type defaultDumper struct {
14 | c *Connection
15 | }
16 |
17 | func newDefaultDumper() *defaultDumper {
18 | return &defaultDumper{}
19 | }
20 |
21 | func (d *defaultDumper) GetCmdEnvVariables() []string {
22 | return []string{}
23 | }
24 |
25 | func (d *defaultDumper) SetConnectionOptions(_ context.Context, c *Connection) error {
26 | d.c = c
27 | return nil
28 | }
29 |
30 | func (d *defaultDumper) GetDatabaseListQuery(username string) string {
31 | return db.GetDatabaseListQuery(username)
32 | }
33 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/logical/logical.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package logical provides jobs for logical initial operations.
6 | package logical
7 |
8 | import (
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/docker/docker/api/types/mount"
13 | )
14 |
15 | func buildAnalyzeCommand(conn Connection, parallelJobs int) []string {
16 | analyzeCmd := []string{
17 | "vacuumdb",
18 | "--analyze",
19 | "--jobs", strconv.Itoa(parallelJobs),
20 | "--username", conn.Username,
21 | "--all",
22 | }
23 |
24 | return analyzeCmd
25 | }
26 |
27 | func isAlreadyMounted(mounts []mount.Mount, dir string) bool {
28 | dir = strings.Trim(dir, "/")
29 |
30 | for _, mountPoint := range mounts {
31 | if strings.Trim(mountPoint.Source, "/") == dir {
32 | return true
33 | }
34 | }
35 |
36 | return false
37 | }
38 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/physical/custom_test.go:
--------------------------------------------------------------------------------
1 | package physical
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestCustomRecoveryConfig(t *testing.T) {
10 | customTool := newCustomTool(customOptions{
11 | RestoreCommand: "pg_basebackup -X stream -D dataDirectory",
12 | })
13 |
14 | recoveryConfig := customTool.GetRecoveryConfig(11.7)
15 | expectedResponse11 := map[string]string{
16 | "restore_command": "pg_basebackup -X stream -D dataDirectory",
17 | "recovery_target_timeline": "latest",
18 | }
19 | assert.Equal(t, expectedResponse11, recoveryConfig)
20 |
21 | recoveryConfig = customTool.GetRecoveryConfig(12.3)
22 | expectedResponse12 := map[string]string{
23 | "restore_command": "pg_basebackup -X stream -D dataDirectory",
24 | }
25 | assert.Equal(t, expectedResponse12, recoveryConfig)
26 | }
27 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/physical/physical_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package physical
6 |
7 | import (
8 | "bytes"
9 | "context"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/assert"
13 | "github.com/stretchr/testify/require"
14 | )
15 |
16 | func TestInitParamsExtraction(t *testing.T) {
17 | controlDataOutput := bytes.NewBufferString(`
18 | wal_level setting: logical
19 | wal_log_hints setting: on
20 | max_connections setting: 500
21 | max_worker_processes setting: 8
22 | max_prepared_xacts setting: 3
23 | max_locks_per_xact setting: 128
24 | track_commit_timestamp setting: off
25 | max_wal_senders setting: 15
26 | `)
27 |
28 | expectedSettings := map[string]string{
29 | "max_connections": "500",
30 | "max_locks_per_transaction": "128",
31 | "max_prepared_transactions": "3",
32 | "max_worker_processes": "8",
33 | "track_commit_timestamp": "off",
34 | "max_wal_senders": "15",
35 | }
36 |
37 | settings, err := extractControlDataParams(context.Background(), controlDataOutput)
38 |
39 | require.Nil(t, err)
40 | assert.EqualValues(t, settings, expectedSettings)
41 | }
42 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/physical/wal_g_test.go:
--------------------------------------------------------------------------------
1 | package physical
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestWALGRecoveryConfig(t *testing.T) {
10 | walg := newWALG(nil, "dataDir", walgOptions{})
11 |
12 | recoveryConfig := walg.GetRecoveryConfig(11.7)
13 | expectedResponse11 := map[string]string{
14 | "restore_command": "wal-g wal-fetch %f %p",
15 | "recovery_target_timeline": "latest",
16 | }
17 | assert.Equal(t, expectedResponse11, recoveryConfig)
18 |
19 | recoveryConfig = walg.GetRecoveryConfig(12.3)
20 | expectedResponse12 := map[string]string{
21 | "restore_command": "wal-g wal-fetch %f %p",
22 | }
23 | assert.Equal(t, expectedResponse12, recoveryConfig)
24 | }
25 |
26 | func TestWALGVersionParse(t *testing.T) {
27 | version, err := parseWalGVersion("wal-g version v2.0.0\t1eb88a5\t2022.05.20_10:45:57\tPostgreSQL")
28 | assert.NoError(t, err)
29 | assert.NotEmpty(t, version)
30 | assert.Equal(t, "v2.0.0", version)
31 | }
32 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/snapshot/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package snapshot
6 |
7 | type skipSnapshotErr struct {
8 | message string
9 | }
10 |
11 | func newSkipSnapshotErr(message string) *skipSnapshotErr {
12 | return &skipSnapshotErr{message: message}
13 | }
14 |
15 | func (e *skipSnapshotErr) Error() string {
16 | return e.message
17 | }
18 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/snapshot/snapshot.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package snapshot provides components for preparing initial snapshots.
6 | package snapshot
7 |
8 | import (
9 | "github.com/pkg/errors"
10 |
11 | "gitlab.com/postgres-ai/database-lab/v3/internal/provision/runners"
12 | "gitlab.com/postgres-ai/database-lab/v3/internal/retrieval/dbmarker"
13 | "gitlab.com/postgres-ai/database-lab/v3/pkg/log"
14 | )
15 |
16 | func extractDataStateAt(dbMarker *dbmarker.Marker) string {
17 | dbMark, err := dbMarker.GetConfig()
18 | if err != nil {
19 | log.Msg("Cannot retrieve dataStateAt from DBMarker config:", err)
20 | return ""
21 | }
22 |
23 | return dbMark.DataStateAt
24 | }
25 |
26 | func runPreprocessingScript(preprocessingScript string) error {
27 | commandOutput, err := runners.NewLocalRunner(false).Run(preprocessingScript)
28 | if err != nil {
29 | return errors.Wrap(err, "failed to run custom script")
30 | }
31 |
32 | log.Msg(commandOutput)
33 |
34 | return nil
35 | }
36 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/tools/activity/activity.go:
--------------------------------------------------------------------------------
1 | // Package activity observes activities of the data retrieval process.
2 | package activity
3 |
4 | // Activity represents job activity.
5 | type Activity struct {
6 | Source []PGEvent
7 | Target []PGEvent
8 | }
9 |
10 | // PGEvent represents pg_stat_activity event.
11 | type PGEvent struct {
12 | User string
13 | Duration float64
14 | Query string
15 | WaitEventType string
16 | WaitEvent string
17 | }
18 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/engine/postgres/tools/defaults/defaults.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package defaults contains default values.
6 | package defaults
7 |
8 | const (
9 | // Port defines a default port.
10 | Port = 5432
11 |
12 | // Username defines a default user name.
13 | Username = "postgres"
14 |
15 | // DBName defines a default database name.
16 | DBName = "postgres"
17 |
18 | // PGVersion12 defines the PostgreSQL 12 version.
19 | PGVersion12 = 12
20 | )
21 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/options/options.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package options provides helpers to process retrieval options.
6 | package options
7 |
8 | import (
9 | "gopkg.in/yaml.v2"
10 | )
11 |
12 | // Unmarshal converts configuration to specific options.
13 | func Unmarshal(in, out interface{}) error {
14 | // TODO: Parse default yaml values in tags.
15 | b, err := yaml.Marshal(in)
16 | if err != nil {
17 | return err
18 | }
19 |
20 | return yaml.Unmarshal(b, out)
21 | }
22 |
--------------------------------------------------------------------------------
/engine/internal/retrieval/refresh_errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | package retrieval
6 |
7 | // SkipRefreshingError defines an error when data refreshing is skipped.
8 | type SkipRefreshingError struct {
9 | msg string
10 | }
11 |
12 | // NewSkipRefreshingError creates a new SkipRefreshingError.
13 | func NewSkipRefreshingError(msg string) *SkipRefreshingError {
14 | return &SkipRefreshingError{
15 | msg: msg,
16 | }
17 | }
18 |
19 | // Error returns error message.
20 | func (e *SkipRefreshingError) Error() string {
21 | return e.msg
22 | }
23 |
--------------------------------------------------------------------------------
/engine/internal/runci/source/source.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | // Package source provides a tools to use version control systems.
6 | package source
7 |
8 | import (
9 | "context"
10 | )
11 |
12 | const (
13 | // RepoDir defines a directory to clone and extract repository.
14 | RepoDir = "/tmp/ci_checker"
15 | )
16 |
17 | // Config describes the configuration of the plugged version control system.
18 | type Config struct {
19 | Type string `yaml:"type"`
20 | Token string `yaml:"token"`
21 | }
22 |
23 | // Provider declares code provider interface.
24 | type Provider interface {
25 | Download(ctx context.Context, opts Opts, output string) error
26 | Extract(file string) (sourceCodeDir string, err error)
27 | }
28 |
29 | // Opts declares repository options.
30 | type Opts struct {
31 | Owner string `json:"owner"`
32 | Repo string `json:"repo"`
33 | Ref string `json:"ref"`
34 | Branch string `json:"branch"`
35 | BranchLink string `json:"branch_link"`
36 | Commit string `json:"commit"`
37 | CommitLink string `json:"commit_link"`
38 | RequestLink string `json:"request_link"`
39 | DiffLink string `json:"diff_link"`
40 | }
41 |
--------------------------------------------------------------------------------
/engine/internal/srv/api/errors_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package api
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 |
12 | "gitlab.com/postgres-ai/database-lab/v3/pkg/models"
13 | )
14 |
15 | func TestErrorCodeStatuses(t *testing.T) {
16 | testCases := []struct {
17 | error models.ErrorCode
18 | code int
19 | }{
20 | {
21 | error: "BAD_REQUEST",
22 | code: 400,
23 | },
24 | {
25 | error: "UNAUTHORIZED",
26 | code: 401,
27 | },
28 | {
29 | error: "NOT_FOUND",
30 | code: 404,
31 | },
32 | {
33 | error: "INTERNAL_ERROR",
34 | code: 500,
35 | },
36 | {
37 | error: "UNKNOWN_ERROR",
38 | code: 500,
39 | },
40 | }
41 |
42 | for _, tc := range testCases {
43 | errorCode := toStatusCode(models.Error{Code: tc.error})
44 |
45 | assert.Equal(t, tc.code, errorCode)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/engine/internal/srv/config/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | // Package config contains configuration options of HTTP server.
6 | package config
7 |
8 | // Config provides configuration management via DLE API
9 | type Config struct {
10 | VerificationToken string `yaml:"verificationToken" json:"-"`
11 | Host string `yaml:"host"`
12 | Port uint `yaml:"port"`
13 | DisableConfigModification bool `yaml:"disableConfigModification" json:"-"`
14 | }
15 |
--------------------------------------------------------------------------------
/engine/internal/srv/config_test.go:
--------------------------------------------------------------------------------
1 | package srv
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestCustomOptions(t *testing.T) {
10 | testCases := []struct {
11 | customOptions []interface{}
12 | expectedResult error
13 | }{
14 | {
15 | customOptions: []interface{}{"--verbose"},
16 | expectedResult: nil,
17 | },
18 | {
19 | customOptions: []interface{}{"--exclude-scheme=test_scheme"},
20 | expectedResult: nil,
21 | },
22 | {
23 | customOptions: []interface{}{`--exclude-scheme="test_scheme"`},
24 | expectedResult: nil,
25 | },
26 | {
27 | customOptions: []interface{}{"--table=$(echo 'test')"},
28 | expectedResult: errInvalidOption,
29 | },
30 | {
31 | customOptions: []interface{}{"--table=test&table"},
32 | expectedResult: errInvalidOption,
33 | },
34 | {
35 | customOptions: []interface{}{5},
36 | expectedResult: errInvalidOptionType,
37 | },
38 | }
39 |
40 | for _, tc := range testCases {
41 | validationResult := validateCustomOptions(tc.customOptions)
42 |
43 | require.ErrorIs(t, validationResult, tc.expectedResult)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/engine/internal/srv/mw/logging.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package mw
6 |
7 | import (
8 | "net/http"
9 |
10 | "gitlab.com/postgres-ai/database-lab/v3/pkg/log"
11 | )
12 |
13 | // Logging logs the incoming request.
14 | func Logging(next http.Handler) http.Handler {
15 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16 | log.Msg("-> ", r.Method, r.RequestURI)
17 | next.ServeHTTP(w, r)
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/engine/internal/srv/ws/ping.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/gorilla/websocket"
7 |
8 | "gitlab.com/postgres-ai/database-lab/v3/pkg/log"
9 | )
10 |
11 | const (
12 | // Time allowed to write a message to the peer.
13 | writeWait = 10 * time.Second
14 |
15 | // Time allowed to read the next pong message from the peer.
16 | pongWait = 60 * time.Second
17 |
18 | // Send pings to peer with this period. Must be less than pongWait.
19 | pingPeriod = (pongWait * 9) / 10 //nolint
20 | )
21 |
22 | // Ping sends control messages.
23 | func Ping(ws *websocket.Conn, done chan struct{}) {
24 | ticker := time.NewTicker(pingPeriod)
25 | defer ticker.Stop()
26 |
27 | for {
28 | select {
29 | case <-ticker.C:
30 | if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
31 | log.Err(err)
32 | }
33 |
34 | case <-done:
35 | return
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/engine/internal/validator/validator.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | // Package validator provides a validation service.
6 | package validator
7 |
8 | import (
9 | "fmt"
10 | "strings"
11 |
12 | "github.com/pkg/errors"
13 | passwordvalidator "github.com/wagslane/go-password-validator"
14 |
15 | "gitlab.com/postgres-ai/database-lab/v3/pkg/client/dblabapi/types"
16 | )
17 |
18 | const minEntropyBits = 60
19 |
20 | // Service provides a validation service.
21 | type Service struct {
22 | }
23 |
24 | // ValidateCloneRequest validates a clone request.
25 | func (v Service) ValidateCloneRequest(cloneRequest *types.CloneCreateRequest) error {
26 | if cloneRequest.DB == nil {
27 | return errors.New("missing both DB username and password")
28 | }
29 |
30 | if cloneRequest.DB.Username == "" {
31 | return errors.New("missing DB username")
32 | }
33 |
34 | if cloneRequest.DB.Password == "" {
35 | return errors.New("missing DB password")
36 | }
37 |
38 | if cloneRequest.ID != "" && strings.Contains(cloneRequest.ID, "/") {
39 | return errors.New("Clone ID cannot contain slash ('/'). Please choose another ID")
40 | }
41 |
42 | if err := passwordvalidator.Validate(cloneRequest.DB.Password, minEntropyBits); err != nil {
43 | return fmt.Errorf("password validation: %w", err)
44 | }
45 |
46 | return nil
47 | }
48 |
--------------------------------------------------------------------------------
/engine/pkg/client/dblabapi/snapshot.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package dblabapi
6 |
7 | import (
8 | "context"
9 | "encoding/json"
10 | "io"
11 | "net/http"
12 |
13 | "github.com/pkg/errors"
14 |
15 | "gitlab.com/postgres-ai/database-lab/v3/pkg/models"
16 | )
17 |
18 | // ListSnapshots provides a snapshot list.
19 | func (c *Client) ListSnapshots(ctx context.Context) ([]*models.Snapshot, error) {
20 | body, err := c.ListSnapshotsRaw(ctx)
21 | if err != nil {
22 | return nil, errors.Wrap(err, "failed to get response")
23 | }
24 |
25 | defer func() { _ = body.Close() }()
26 |
27 | var snapshots []*models.Snapshot
28 |
29 | if err := json.NewDecoder(body).Decode(&snapshots); err != nil {
30 | return nil, errors.Wrap(err, "failed to get response")
31 | }
32 |
33 | return snapshots, nil
34 | }
35 |
36 | // ListSnapshotsRaw provides a snapshot list in raw format.
37 | func (c *Client) ListSnapshotsRaw(ctx context.Context) (io.ReadCloser, error) {
38 | u := c.URL("/snapshots")
39 |
40 | request, err := http.NewRequest(http.MethodGet, u.String(), nil)
41 | if err != nil {
42 | return nil, errors.Wrap(err, "failed to make a request")
43 | }
44 |
45 | response, err := c.Do(ctx, request)
46 | if err != nil {
47 | return nil, errors.Wrap(err, "failed to get response")
48 | }
49 |
50 | return response.Body, nil
51 | }
52 |
--------------------------------------------------------------------------------
/engine/pkg/client/dblabapi/types/clone.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package types provides request structures for Database Lab HTTP API.
6 | package types
7 |
8 | // CloneCreateRequest represents clone params of a create request.
9 | type CloneCreateRequest struct {
10 | ID string `json:"id"`
11 | Protected bool `json:"protected"`
12 | DB *DatabaseRequest `json:"db"`
13 | Snapshot *SnapshotCloneFieldRequest `json:"snapshot"`
14 | ExtraConf map[string]string `json:"extra_conf"`
15 | }
16 |
17 | // CloneUpdateRequest represents params of an update request.
18 | type CloneUpdateRequest struct {
19 | Protected bool `json:"protected"`
20 | }
21 |
22 | // DatabaseRequest represents database params of a clone request.
23 | type DatabaseRequest struct {
24 | Username string `json:"username"`
25 | Password string `json:"password"`
26 | Restricted bool `json:"restricted"`
27 | DBName string `json:"db_name"`
28 | }
29 |
30 | // SnapshotCloneFieldRequest represents snapshot params of a create request.
31 | type SnapshotCloneFieldRequest struct {
32 | ID string `json:"id"`
33 | }
34 |
35 | // ResetCloneRequest represents snapshot params of a reset request.
36 | type ResetCloneRequest struct {
37 | SnapshotID string `json:"snapshotID"`
38 | Latest bool `json:"latest"`
39 | }
40 |
--------------------------------------------------------------------------------
/engine/pkg/client/dblabapi/types/observation.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package types
6 |
7 | // StartObservationRequest represents a request for the start observation endpoint.
8 | type StartObservationRequest struct {
9 | CloneID string `json:"clone_id"`
10 | Config Config `json:"config"`
11 | Tags map[string]string `json:"tags"`
12 | DBName string `json:"db_name"`
13 | }
14 |
15 | // Config defines configuration options for observer.
16 | type Config struct {
17 | ObservationInterval uint64 `json:"observation_interval"`
18 | MaxLockDuration uint64 `json:"max_lock_duration"`
19 | MaxDuration uint64 `json:"max_duration"`
20 | }
21 |
22 | // StopObservationRequest represents a request for the stop observation endpoint.
23 | type StopObservationRequest struct {
24 | CloneID string `json:"clone_id"`
25 | OverallError bool `json:"overall_error"`
26 | }
27 |
--------------------------------------------------------------------------------
/engine/pkg/client/platform/token.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | // Package platform provides the Platform API client.
6 | package platform
7 |
8 | import (
9 | "context"
10 | "fmt"
11 |
12 | "github.com/pkg/errors"
13 |
14 | "gitlab.com/postgres-ai/database-lab/v3/pkg/log"
15 | )
16 |
17 | // TokenCheckRequest represents a token checking request.
18 | type TokenCheckRequest struct {
19 | Token string `json:"token"`
20 | }
21 |
22 | // TokenCheckResponse represents response of a token checking request.
23 | type TokenCheckResponse struct {
24 | APIResponse
25 | OrganizationID uint `json:"org_id"`
26 | Personal bool `json:"is_personal"`
27 | }
28 |
29 | // CheckPlatformToken makes an HTTP request to check the Platform Access Token.
30 | func (p *Client) CheckPlatformToken(ctx context.Context, request TokenCheckRequest) (TokenCheckResponse, error) {
31 | respData := TokenCheckResponse{}
32 |
33 | if err := p.doPost(ctx, "/rpc/dblab_token_check", request, &respData); err != nil {
34 | return respData, errors.Wrap(err, "failed to post request")
35 | }
36 |
37 | if respData.Code != "" || respData.Message != "" {
38 | log.Dbg(fmt.Sprintf("Unsuccessful response given. Request: %v", request))
39 |
40 | return respData, errors.Errorf("error: %v", respData)
41 | }
42 |
43 | return respData, nil
44 | }
45 |
--------------------------------------------------------------------------------
/engine/pkg/models/clone.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package models
6 |
7 | // Clone defines a clone model.
8 | type Clone struct {
9 | ID string `json:"id"`
10 | Snapshot *Snapshot `json:"snapshot"`
11 | Protected bool `json:"protected"`
12 | DeleteAt *LocalTime `json:"deleteAt"`
13 | CreatedAt *LocalTime `json:"createdAt"`
14 | Status Status `json:"status"`
15 | DB Database `json:"db"`
16 | Metadata CloneMetadata `json:"metadata"`
17 | }
18 |
19 | // CloneMetadata contains fields describing a clone model.
20 | type CloneMetadata struct {
21 | CloneDiffSize uint64 `json:"cloneDiffSize"`
22 | LogicalSize uint64 `json:"logicalSize"`
23 | CloningTime float64 `json:"cloningTime"`
24 | MaxIdleMinutes uint `json:"maxIdleMinutes"`
25 | }
26 |
27 | // CloneView represents a view of clone model.
28 | type CloneView struct {
29 | *Clone
30 | Snapshot *SnapshotView `json:"snapshot"`
31 | Metadata CloneMetadataView `json:"metadata"`
32 | }
33 |
34 | // CloneMetadataView represents a view of clone metadata.
35 | type CloneMetadataView struct {
36 | *CloneMetadata
37 | CloneDiffSize Size `json:"cloneDiffSize"`
38 | LogicalSize Size `json:"logicalSize"`
39 | }
40 |
--------------------------------------------------------------------------------
/engine/pkg/models/database.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package models
6 |
7 | // Database defines clone database parameters.
8 | type Database struct {
9 | ConnStr string `json:"connStr"`
10 | Host string `json:"host"`
11 | Port string `json:"port"`
12 | Username string `json:"username"`
13 | Password string `json:"password"`
14 | DBName string `json:"dbName"`
15 | }
16 |
--------------------------------------------------------------------------------
/engine/pkg/models/error.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | // Package models provides Database Lab struct.
6 | package models
7 |
8 | // ErrorCode defines a response error type.
9 | type ErrorCode string
10 |
11 | // ErrCode constants define a response error codes.
12 | const (
13 | ErrCodeInternal ErrorCode = "INTERNAL_ERROR"
14 | ErrCodeBadRequest ErrorCode = "BAD_REQUEST"
15 | ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED"
16 | ErrCodeNotFound ErrorCode = "NOT_FOUND"
17 | )
18 |
19 | // Error struct represents a response error.
20 | type Error struct {
21 | Code ErrorCode `json:"code"`
22 | Message string `json:"message"`
23 | }
24 |
25 | var _ error = &Error{}
26 |
27 | // New creates ClientError instance with given code and message.
28 | func New(code ErrorCode, message string) *Error {
29 | return &Error{
30 | Code: code,
31 | Message: message,
32 | }
33 | }
34 |
35 | // Error prints an error message.
36 | func (e Error) Error() string {
37 | return e.Message
38 | }
39 |
--------------------------------------------------------------------------------
/engine/pkg/models/fs.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package models
6 |
7 | import (
8 | "fmt"
9 | "math/big"
10 |
11 | "github.com/dustin/go-humanize"
12 | )
13 |
14 | // FileSystem describes state of a file system.
15 | type FileSystem struct {
16 | Mode string `json:"mode"`
17 | Size uint64 `json:"size"`
18 | Free uint64 `json:"free"`
19 | Used uint64 `json:"used"`
20 | DataSize uint64 `json:"dataSize"`
21 | UsedBySnapshots uint64 `json:"usedBySnapshots"`
22 | UsedByClones uint64 `json:"usedByClones"`
23 | CompressRatio float64 `json:"compressRatio"`
24 | }
25 |
26 | // FileSystemView describes a view of file system state.
27 | type FileSystemView struct {
28 | *FileSystem
29 | Size Size `json:"size"`
30 | Free Size `json:"free"`
31 | Used Size `json:"used"`
32 | DataSize Size `json:"dataSize"`
33 | UsedBySnapshots Size `json:"usedBySnapshots"`
34 | UsedByClones Size `json:"usedByClones"`
35 | }
36 |
37 | // Size describes amount of disk space.
38 | type Size uint64
39 |
40 | // MarshalJSON marshals the Size struct.
41 | func (s Size) MarshalJSON() ([]byte, error) {
42 | humanReadableSize := humanize.BigIBytes(big.NewInt(int64(s)))
43 | return []byte(fmt.Sprintf("%q", humanReadableSize)), nil
44 | }
45 |
--------------------------------------------------------------------------------
/engine/pkg/models/local_time.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2022 © Postgres.ai
3 | */
4 |
5 | package models
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "time"
11 | )
12 |
13 | const legacyFormat = "2006-01-02 15:04:05 UTC"
14 |
15 | // LocalTime defines a type of time with custom marshalling depends on locale.
16 | type LocalTime struct {
17 | time.Time
18 | }
19 |
20 | // NewLocalTime creates a new instance of LocalTime.
21 | func NewLocalTime(t time.Time) *LocalTime {
22 | return &LocalTime{Time: t}
23 | }
24 |
25 | // UnmarshalJSON un-marshals LocalTime.
26 | func (t *LocalTime) UnmarshalJSON(data []byte) error {
27 | localTime := bytes.Trim(data, "\"")
28 |
29 | if len(localTime) == 0 {
30 | return nil
31 | }
32 |
33 | parsedTime, err := time.Parse(time.RFC3339, string(localTime))
34 | if err != nil {
35 | // Try to parse the legacy format to keep backward compatibility when restore clone sessions.
36 | parsedTime, err = time.Parse(legacyFormat, string(localTime))
37 | if err != nil {
38 | return err
39 | }
40 | }
41 |
42 | t.Time = parsedTime
43 |
44 | return nil
45 | }
46 |
47 | // MarshalJSON marshals LocalTime.
48 | func (t *LocalTime) MarshalJSON() ([]byte, error) {
49 | if t.IsZero() {
50 | return []byte(`""`), nil
51 | }
52 |
53 | return []byte(fmt.Sprintf("%q", t.Local().Format(time.RFC3339))), nil
54 | }
55 |
--------------------------------------------------------------------------------
/engine/pkg/models/observation.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package models
6 |
7 | import (
8 | "time"
9 | )
10 |
11 | // ObservationResult represents a result of observation session.
12 | type ObservationResult struct {
13 | Status string `json:"status"`
14 | Intervals []Interval `json:"intervals"`
15 | Summary Summary `json:"summary"`
16 | }
17 |
18 | // Interval represents data of an observation interval.
19 | type Interval struct {
20 | StartedAt time.Time `json:"started_at"`
21 | Duration float64 `json:"duration"`
22 | Warning string `json:"warning"`
23 | }
24 |
25 | // Summary represents a summary of observation.
26 | type Summary struct {
27 | TotalDuration float64 `json:"total_duration"`
28 | TotalIntervals uint `json:"total_intervals"`
29 | WarningIntervals uint `json:"warning_intervals"`
30 | Checklist Checklist `json:"checklist"`
31 | }
32 |
33 | // Checklist represents a list of observation checks.
34 | type Checklist struct {
35 | Success bool `json:"overall_success"`
36 | Duration bool `json:"session_duration_acceptable"`
37 | Locks bool `json:"no_long_dangerous_locks"`
38 | }
39 |
--------------------------------------------------------------------------------
/engine/pkg/models/retrieval_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | package models
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestLevelByAlertType(t *testing.T) {
14 | testCases := []struct {
15 | alertType AlertType
16 | level AlertLevel
17 | }{
18 | {
19 | alertType: "refresh_failed",
20 | level: ErrorLevel,
21 | },
22 | {
23 | alertType: "refresh_skipped",
24 | level: WarningLevel,
25 | },
26 | {
27 | alertType: "unknown_fail",
28 | level: UnknownLevel,
29 | },
30 | }
31 |
32 | for _, tc := range testCases {
33 | level := AlertLevelByType(tc.alertType)
34 | assert.Equal(t, tc.level, level)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/engine/pkg/models/snapshot.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package models
6 |
7 | // Snapshot defines a snapshot entity.
8 | type Snapshot struct {
9 | ID string `json:"id"`
10 | CreatedAt *LocalTime `json:"createdAt"`
11 | DataStateAt *LocalTime `json:"dataStateAt"`
12 | PhysicalSize uint64 `json:"physicalSize"`
13 | LogicalSize uint64 `json:"logicalSize"`
14 | Pool string `json:"pool"`
15 | NumClones int `json:"numClones"`
16 | }
17 |
18 | // SnapshotView represents a view of snapshot.
19 | type SnapshotView struct {
20 | *Snapshot
21 | PhysicalSize Size `json:"physicalSize"`
22 | LogicalSize Size `json:"logicalSize"`
23 | }
24 |
--------------------------------------------------------------------------------
/engine/pkg/models/sync.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // Sync defines the status of synchronization containers
4 | type Sync struct {
5 | Status Status `json:"status"`
6 | StartedAt string `json:"startedAt,omitempty"`
7 | LastReplayedLsn string `json:"lastReplayedLsn"`
8 | LastReplayedLsnAt string `json:"lastReplayedLsnAt"`
9 | ReplicationLag int `json:"replicationLag"`
10 | ReplicationUptime int `json:"replicationUptime"`
11 | }
12 |
--------------------------------------------------------------------------------
/engine/pkg/util/backup/backup.go:
--------------------------------------------------------------------------------
1 | // Package backup utilities to back up and restore data
2 | package backup
3 |
4 | import (
5 | "time"
6 | )
7 |
8 | const (
9 | backupFileExtension = ".bak"
10 | )
11 |
12 | type backup struct {
13 | Filename string
14 | Time time.Time
15 | }
16 |
17 | var now = func() time.Time {
18 | return time.Now().In(time.UTC)
19 | }
20 |
--------------------------------------------------------------------------------
/engine/pkg/util/backup/utils.go:
--------------------------------------------------------------------------------
1 | package backup
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 | "strings"
7 | "time"
8 |
9 | "github.com/pkg/errors"
10 |
11 | "gitlab.com/postgres-ai/database-lab/v3/pkg/util"
12 | )
13 |
14 | // getFileTimestamp returns the timestamp of a backup file.
15 | // expected filename format: ...bak
16 | func getFileTimestamp(filename string) (time.Time, error) {
17 | base := filepath.Base(filename)
18 | split := strings.Split(base, ".")
19 |
20 | const expectedSize = 3
21 |
22 | if len(split) < expectedSize {
23 | return time.Time{}, fmt.Errorf("invalid filename format: %s", filename)
24 | }
25 |
26 | timeStr := split[len(split)-2]
27 | timeStamp, err := time.Parse(util.DataStateAtFormat, timeStr)
28 |
29 | if err != nil {
30 | return time.Time{}, errors.Wrap(err, "failed to parse timestamp")
31 | }
32 |
33 | return timeStamp, nil
34 | }
35 |
--------------------------------------------------------------------------------
/engine/pkg/util/bytes.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | // Package util provides utility functions. Data size related functions.
6 | package util
7 |
8 | import (
9 | "crypto/sha1"
10 | "encoding/hex"
11 | "strconv"
12 | )
13 |
14 | // ParseBytes returns number of bytes from string.
15 | func ParseBytes(str string) (uint64, error) {
16 | return strconv.ParseUint(str, 10, 64)
17 | }
18 |
19 | // HashID returns a hash of provided string.
20 | func HashID(id string) string {
21 | h := sha1.New()
22 | _, _ = h.Write([]byte(id))
23 |
24 | return hex.EncodeToString(h.Sum(nil))
25 | }
26 |
--------------------------------------------------------------------------------
/engine/pkg/util/clones.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2020 © Postgres.ai
3 | */
4 |
5 | package util
6 |
7 | import (
8 | "strconv"
9 | )
10 |
11 | const (
12 | // ClonePrefix defines a Database Lab clone prefix.
13 | ClonePrefix = "dblab_clone_"
14 | )
15 |
16 | // GetCloneName returns a clone name.
17 | func GetCloneName(port uint) string {
18 | return ClonePrefix + strconv.FormatUint(uint64(port), 10)
19 | }
20 |
21 | // GetCloneNameStr returns a clone name.
22 | func GetCloneNameStr(port string) string {
23 | return ClonePrefix + port
24 | }
25 |
--------------------------------------------------------------------------------
/engine/pkg/util/engine/engine.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | // Package engine contains tools.
6 | package engine
7 |
8 | // DefaultListenerHost defines the default host of an HTTP listener.
9 | const DefaultListenerHost = "0.0.0.0"
10 |
11 | // HTTPScheme defines the default HTTP scheme.
12 | const HTTPScheme = "http"
13 |
14 | // Localhost defines the address of the localhost host.
15 | const Localhost = "127.0.0.1"
16 |
--------------------------------------------------------------------------------
/engine/pkg/util/networks/networks_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | package networks
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/docker/docker/api/types"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestInternalNetworks(t *testing.T) {
15 | t.Run("test internal network naming", func(t *testing.T) {
16 | instanceID := "testInstanceID"
17 |
18 | assert.Equal(t, "dle_network_testInstanceID", getNetworkName(instanceID))
19 | })
20 | }
21 |
22 | func TestIfContainerConnected(t *testing.T) {
23 | t.Run("test if container connected", func(t *testing.T) {
24 | resource := types.NetworkResource{
25 | Containers: map[string]types.EndpointResource{
26 | "testID": {Name: "test_server"},
27 | },
28 | }
29 | testCases := []struct {
30 | containerName string
31 | result bool
32 | }{
33 | {
34 | containerName: "test_server",
35 | result: true,
36 | },
37 | {
38 | containerName: "not_connected_server",
39 | result: false,
40 | },
41 | }
42 |
43 | for _, tc := range testCases {
44 | assert.Equal(t, tc.result, hasContainerConnected(resource, tc.containerName))
45 | }
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/engine/pkg/util/projection/helpers.go:
--------------------------------------------------------------------------------
1 | // Package projection helps to bind struct fields to json/yaml paths.
2 | package projection
3 |
4 | import (
5 | "gopkg.in/yaml.v3"
6 | )
7 |
8 | // LoadYaml loads struct fields from yaml document.
9 | func LoadYaml(target interface{}, yaml *yaml.Node, options LoadOptions) error {
10 | soft, err := NewSoftYaml(yaml)
11 | if err != nil {
12 | return err
13 | }
14 |
15 | return Load(target, soft, options)
16 | }
17 |
18 | // StoreYaml stores struct fields to yaml document.
19 | func StoreYaml(target interface{}, yaml *yaml.Node, options StoreOptions) error {
20 | soft, err := NewSoftYaml(yaml)
21 | if err != nil {
22 | return err
23 | }
24 |
25 | return Store(target, soft, options)
26 | }
27 |
28 | // LoadJSON loads struct fields from json document.
29 | func LoadJSON(target interface{}, m map[string]interface{}, options LoadOptions) error {
30 | soft := NewSoftJSON(m)
31 | return Load(target, soft, options)
32 | }
33 |
34 | // StoreJSON stores struct fields to json document.
35 | func StoreJSON(target interface{}, m map[string]interface{}, options StoreOptions) error {
36 | soft := NewSoftJSON(m)
37 | return Store(target, soft, options)
38 | }
39 |
--------------------------------------------------------------------------------
/engine/pkg/util/projection/load_json_test.go:
--------------------------------------------------------------------------------
1 | package projection
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestLoadJson(t *testing.T) {
10 | r := require.New(t)
11 | s := &testStruct{}
12 |
13 | err := LoadJSON(s, getJSONNormal(), LoadOptions{})
14 | r.NoError(err)
15 |
16 | requireComplete(t, s)
17 | requireMissEmpty(t, s)
18 | }
19 |
20 | func TestLoadJsonNull(t *testing.T) {
21 | r := require.New(t)
22 | s := fullTestStruct()
23 |
24 | err := LoadJSON(s, getJSONNull(), LoadOptions{})
25 | r.NoError(err)
26 |
27 | requireEmpty(t, s)
28 | }
29 |
--------------------------------------------------------------------------------
/engine/pkg/util/projection/load_yaml_test.go:
--------------------------------------------------------------------------------
1 | package projection
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestLoadYaml(t *testing.T) {
10 | r := require.New(t)
11 | s := &testStruct{}
12 | node := getYamlNormal(t)
13 |
14 | err := LoadYaml(s, node, LoadOptions{})
15 | r.NoError(err)
16 |
17 | requireMissEmpty(t, s)
18 | requireComplete(t, s)
19 | }
20 |
21 | func TestLoadYamlNull(t *testing.T) {
22 | r := require.New(t)
23 | s := fullTestStruct()
24 | node := getYamlNull(t)
25 |
26 | err := LoadYaml(s, node, LoadOptions{})
27 | r.NoError(err)
28 |
29 | requireEmpty(t, s)
30 | }
31 |
--------------------------------------------------------------------------------
/engine/pkg/util/projection/multi_group_test.go:
--------------------------------------------------------------------------------
1 | package projection
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | "gopkg.in/yaml.v3"
8 | )
9 |
10 | type structMulti struct {
11 | Yaml string `proj:"yaml.yamlValue" groups:"yaml"`
12 | JSON string `proj:"json.jsonValue" groups:"json"`
13 | }
14 |
15 | const yamlMulti = `
16 | yaml:
17 | yamlValue: "yamlValue"
18 | `
19 |
20 | func getJSONMulti() map[string]interface{} {
21 | return map[string]interface{}{
22 | "json": map[string]interface{}{
23 | "jsonValue": "jsonValue",
24 | },
25 | }
26 | }
27 |
28 | func getYamlMulti(t *testing.T) *yaml.Node {
29 | t.Helper()
30 | node := &yaml.Node{}
31 | err := yaml.Unmarshal([]byte(yamlMulti), node)
32 | require.NoError(t, err)
33 | return node
34 | }
35 |
36 | func TestLoadJsonMulti(t *testing.T) {
37 | r := require.New(t)
38 |
39 | s := &structMulti{}
40 | err := LoadJSON(s, getJSONMulti(), LoadOptions{
41 | Groups: []string{"json"},
42 | })
43 | r.NoError(err)
44 |
45 | err = LoadYaml(s, getYamlMulti(t), LoadOptions{
46 | Groups: []string{"yaml"},
47 | })
48 | r.NoError(err)
49 |
50 | r.Equal("jsonValue", s.JSON)
51 | r.Equal("yamlValue", s.Yaml)
52 | }
53 |
--------------------------------------------------------------------------------
/engine/pkg/util/projection/store_yaml_test.go:
--------------------------------------------------------------------------------
1 | package projection
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestStoreYaml(t *testing.T) {
10 | r := require.New(t)
11 | s := fullTestStruct()
12 | node := getYamlDiverted(t)
13 |
14 | err := StoreYaml(s, node, StoreOptions{})
15 | r.NoError(err)
16 |
17 | requireYamlNormal(t, node)
18 | }
19 |
20 | func TestStoreYamlNull(t *testing.T) {
21 | r := require.New(t)
22 | s := &testStruct{}
23 | node := getYamlNormal(t)
24 |
25 | err := StoreYaml(s, node, StoreOptions{})
26 | r.NoError(err)
27 |
28 | // no changes should have been made to the node
29 | requireYamlNullApplied(t, node)
30 | }
31 |
--------------------------------------------------------------------------------
/engine/pkg/util/projection/types.go:
--------------------------------------------------------------------------------
1 | package projection
2 |
3 | import "gitlab.com/postgres-ai/database-lab/v3/pkg/util/ptypes"
4 |
5 | // FieldSet represents a field to be set in the accessor.
6 | type FieldSet struct {
7 | Path []string
8 | Value interface{}
9 | Type ptypes.Type
10 | CreateKey bool
11 | }
12 |
13 | // FieldGet is used to retrieve a field value from an accessor.
14 | type FieldGet struct {
15 | Path []string
16 | Type ptypes.Type
17 | }
18 |
19 | // Accessor is an interface for getting and setting values from a json / yaml / anything else
20 | type Accessor interface {
21 | Set(set FieldSet) error
22 | Get(get FieldGet) (interface{}, error)
23 | }
24 |
--------------------------------------------------------------------------------
/engine/pkg/util/slices.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | // Package util provides utility functions. Slices related utils.
6 | package util
7 |
8 | // Unique returns unique values of slice.
9 | func Unique(list []string) []string {
10 | keys := make(map[string]struct{})
11 | uqList := []string{}
12 |
13 | for _, entry := range list {
14 | if _, value := keys[entry]; !value {
15 | keys[entry] = struct{}{}
16 |
17 | uqList = append(uqList, entry)
18 | }
19 | }
20 |
21 | return uqList
22 | }
23 |
24 | // IncludesString checks if a string is included in a slice.
25 | func IncludesString(list []string, value string) bool {
26 | for _, entry := range list {
27 | if entry == value {
28 | return true
29 | }
30 | }
31 |
32 | return false
33 | }
34 |
--------------------------------------------------------------------------------
/engine/pkg/util/slices_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package util
6 |
7 | import (
8 | "testing"
9 | )
10 |
11 | func TestUnique(t *testing.T) {
12 | if s := Unique([]string{"x", "y", "z"}); len(s) != 3 {
13 | t.FailNow()
14 | }
15 |
16 | if s := Unique([]string{"x", "y", "y"}); len(s) != 2 {
17 | t.FailNow()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/engine/pkg/util/testing.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | // Package util provides utility functions. Testing related.
6 | package util
7 |
8 | import (
9 | "github.com/sergi/go-diff/diffmatchpatch"
10 | )
11 |
12 | func diff(a string, b string) string {
13 | dmp := diffmatchpatch.New()
14 | diffs := dmp.DiffMain(a, b, false)
15 |
16 | return dmp.DiffPrettyText(diffs)
17 | }
18 |
--------------------------------------------------------------------------------
/engine/pkg/util/yaml/custom.go:
--------------------------------------------------------------------------------
1 | // Package yaml contains utilities to work with YAML nodes
2 | package yaml
3 |
4 | import (
5 | "strings"
6 |
7 | "gopkg.in/yaml.v3"
8 | )
9 |
10 | var secretKeyList = []string{"secret", "key", "token", "password"}
11 |
12 | // TraverseNode traverses node and mask sensitive keys.
13 | func TraverseNode(node *yaml.Node) {
14 | switch node.Kind {
15 | case yaml.DocumentNode:
16 | if len(node.Content) < 1 {
17 | return
18 | }
19 |
20 | TraverseNode(node.Content[0])
21 |
22 | case yaml.MappingNode:
23 | for i := 0; i < len(node.Content); i += 2 {
24 | if node.Content[i+1].Kind == yaml.ScalarNode {
25 | if containsSecret(strings.ToLower(node.Content[i].Value)) {
26 | node.Content[i+1].Value = maskValue
27 | node.Content[i+1].Tag = "!!str"
28 | }
29 |
30 | continue
31 | }
32 |
33 | TraverseNode(node.Content[i+1])
34 | }
35 | }
36 | }
37 |
38 | func containsSecret(key string) bool {
39 | for _, secret := range secretKeyList {
40 | if strings.Contains(key, secret) {
41 | return true
42 | }
43 | }
44 |
45 | return false
46 | }
47 |
--------------------------------------------------------------------------------
/engine/pkg/util/yaml/default.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | // DefaultConfigMask return default config copy configuration
4 | func DefaultConfigMask() *Mask {
5 | sensitive := []string{
6 | "server.verificationToken",
7 | "platform.accessToken",
8 | "retrieval.spec.logicalDump.options.source.connection.password",
9 | }
10 |
11 | return NewMask(sensitive)
12 | }
13 |
--------------------------------------------------------------------------------
/engine/pkg/util/yaml/mask.go:
--------------------------------------------------------------------------------
1 | // Package yaml contains utilities to work with YAML nodes
2 | package yaml
3 |
4 | import (
5 | "strings"
6 |
7 | "gopkg.in/yaml.v3"
8 | )
9 |
10 | const maskValue = "****"
11 |
12 | // Mask is a YAML masking utility
13 | type Mask struct {
14 | paths [][]string
15 | }
16 |
17 | // NewMask creates a new YAML copy configuration.
18 | func NewMask(paths []string) *Mask {
19 | c := &Mask{}
20 |
21 | for _, path := range paths {
22 | pathSplit := strings.Split(path, ".")
23 | c.paths = append(c.paths, pathSplit)
24 | }
25 |
26 | return c
27 | }
28 |
29 | // Yaml copies node values
30 | func (c *Mask) Yaml(node *yaml.Node) {
31 | for i := 0; i < len(c.paths); i++ {
32 | child, found := FindNodeAtPath(node, c.paths[i])
33 | if !found {
34 | continue
35 | }
36 |
37 | if child.Kind != yaml.ScalarNode {
38 | continue
39 | }
40 |
41 | child.Value = maskValue
42 | child.Tag = "!!str"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/engine/pkg/util/yaml/mask_test.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | "gopkg.in/yaml.v3"
8 | )
9 |
10 | const yamlStr = `
11 | root:
12 | sensitive: "fromValue"
13 | nonSensitive: 123
14 | `
15 |
16 | func TestMask(t *testing.T) {
17 | r := require.New(t)
18 | node := &yaml.Node{}
19 |
20 | err := yaml.Unmarshal([]byte(yamlStr), node)
21 | r.NoError(err)
22 |
23 | mask := NewMask([]string{"root.sensitive"})
24 | mask.Yaml(node)
25 |
26 | sensitive, _ := FindNodeAtPathString(node, "root.sensitive")
27 | r.NotNil(sensitive)
28 | r.Equal(maskValue, sensitive.Value)
29 |
30 | nonSensitive, _ := FindNodeAtPathString(node, "root.nonSensitive")
31 | r.NotNil(nonSensitive)
32 | r.Equal("123", nonSensitive.Value)
33 | }
34 |
--------------------------------------------------------------------------------
/engine/pkg/util/yaml/path.go:
--------------------------------------------------------------------------------
1 | // Package yaml Utilities to work with YAML nodes
2 | package yaml
3 |
4 | import (
5 | "strings"
6 |
7 | "gopkg.in/yaml.v3"
8 | )
9 |
10 | // FindNodeAtPathString finds the node at the given path.
11 | func FindNodeAtPathString(node *yaml.Node, path string) (*yaml.Node, bool) {
12 | return FindNodeAtPath(node, strings.Split(path, "."))
13 | }
14 |
15 | // FindNodeAtPath finds the node at the given path.
16 | func FindNodeAtPath(node *yaml.Node, path []string) (*yaml.Node, bool) {
17 | if len(path) == 0 {
18 | return node, true
19 | }
20 |
21 | if node.Kind == yaml.DocumentNode {
22 | if len(node.Content) < 1 {
23 | return nil, false
24 | }
25 |
26 | return FindNodeAtPath(node.Content[0], path)
27 | }
28 |
29 | if node.Kind == yaml.MappingNode {
30 | for i := 0; i < len(node.Content); i += 2 {
31 | if node.Content[i].Value == path[0] {
32 | return FindNodeAtPath(node.Content[i+1], path[1:])
33 | }
34 | }
35 | }
36 |
37 | return nil, false
38 | }
39 |
--------------------------------------------------------------------------------
/engine/scripts/bash_autocomplete:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | : ${PROG:=$(basename ${BASH_SOURCE})}
4 |
5 |
6 |
7 | _cli_bash_autocomplete() {
8 | if [[ "${COMP_WORDS[0]}" != "source" ]]; then
9 | local cur opts base
10 | COMPREPLY=()
11 | cur="${COMP_WORDS[COMP_CWORD]}"
12 | if [[ "$cur" == "-"* ]]; then
13 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
14 | else
15 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
16 | fi
17 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
18 | return 0
19 | fi
20 | }
21 |
22 | complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
23 | unset PROG
24 |
--------------------------------------------------------------------------------
/engine/scripts/ci_docker_build_push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | docker_file=${DOCKER_FILE:-"Dockerfile"}
6 | tags=${TAGS:-""}
7 |
8 | registry_user=${REGISTRY_USER:-"${CI_REGISTRY_USER}"}
9 | registry_password=${REGISTRY_PASSWORD:-"${CI_REGISTRY_PASSWORD}"}
10 | registry=${REGISTRY:-"${CI_REGISTRY}"}
11 |
12 | docker login --username $registry_user --password "${registry_password}" $registry
13 |
14 | tags_build=""
15 | tags_push=""
16 |
17 | IFS=',' read -ra ADDR string <&1 | logger --stderr --tag "cleanup_zfs_snapshot"
18 |
--------------------------------------------------------------------------------
/engine/test/_prerequisites.ubuntu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euxo pipefail
3 |
4 | if [[ -x "$(command -v zfs)" ]] && [[ -x "$(command -v docker)" ]]; then
5 | echo "Docker and ZFS are installed – assume that secondary tools (such as jq or curl) are installed too."
6 | else
7 | # Install dependencies
8 | sudo apt-get update && sudo apt-get install -y \
9 | apt-transport-https \
10 | ca-certificates \
11 | curl \
12 | gnupg-agent \
13 | software-properties-common \
14 | jq
15 |
16 | # Install Docker
17 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
18 |
19 | sudo add-apt-repository \
20 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
21 | $(lsb_release -cs) \
22 | stable"
23 |
24 | sudo apt-get install -y \
25 | docker-ce \
26 | docker-ce-cli \
27 | containerd.io
28 |
29 | # Install ZFS
30 | sudo apt-get install -y \
31 | zfsutils-linux
32 |
33 | # Install psql
34 | sudo apt-get install -y \
35 | postgresql-client
36 |
37 | # Install yq
38 | if ! command -v yq &> /dev/null; then
39 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64 && \
40 | sudo add-apt-repository ppa:rmescandon/yq && \
41 | sudo apt-get update && sudo apt-get install yq -y
42 | fi
43 | fi
44 |
--------------------------------------------------------------------------------
/engine/test/_zfs.file.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euxo pipefail
3 |
4 | DLE_TEST_MOUNT_DIR="/var/lib/test/dblab_mount"
5 | DLE_TEST_POOL_NAME="test_dblab_pool"
6 | ZFS_FILE="$(pwd)/zfs_file"
7 |
8 | # If previous run was interrupted without cleanup,
9 | # test_dblab_pool and $ZFS_FILE are still here. Cleanup.
10 | sudo zpool destroy test_dblab_pool || true
11 | sudo rm -f "${ZFS_FILE}"
12 |
13 | truncate --size 1GB "${ZFS_FILE}"
14 |
15 | sudo zpool create -f \
16 | -O compression=on \
17 | -O atime=off \
18 | -O recordsize=128k \
19 | -O logbias=throughput \
20 | -m ${DLE_TEST_MOUNT_DIR}/${DLE_TEST_POOL_NAME} \
21 | test_dblab_pool \
22 | "${ZFS_FILE}"
23 |
24 | sudo zfs list
25 |
--------------------------------------------------------------------------------
/engine/testdata/testdata.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2021 © Postgres.ai
3 | */
4 |
5 | // Package testdata contains data for running tests.
6 | package testdata
7 |
8 | import (
9 | "path"
10 | "runtime"
11 | )
12 |
13 | // GetTestDataDir provides the current directory name.
14 | func GetTestDataDir() string {
15 | _, filename, _, _ := runtime.Caller(0)
16 |
17 | return path.Dir(filename)
18 | }
19 |
--------------------------------------------------------------------------------
/engine/version/version.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | // Package version provides the Database Lab version info.
6 | package version
7 |
8 | import (
9 | "fmt"
10 | )
11 |
12 | // ldflag variables.
13 | var (
14 | version string
15 | buildTime string
16 | )
17 |
18 | // GetVersion return the app version info.
19 | func GetVersion() string {
20 | return fmt.Sprintf("%s-%s", version, buildTime)
21 | }
22 |
--------------------------------------------------------------------------------
/engine/version/version_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | 2019 © Postgres.ai
3 | */
4 |
5 | package version
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestGetVersion(t *testing.T) {
14 | version = "0.0.1"
15 | buildTime = "20200427-0551"
16 |
17 | assert.Equal(t, "0.0.1-20200427-0551", GetVersion())
18 | }
19 |
--------------------------------------------------------------------------------
/translations/unfinished-review-needed/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ui/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules/**
2 | /.vscode/
3 | /.idea/
4 | /bin/
5 | /.git/
6 | **/build/**
7 | ui/node_modules/
8 | ui/packages/ce/node_modules/
9 | ui/packages/shared/node_modules/
10 |
--------------------------------------------------------------------------------
/ui/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | *.log
3 |
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, build with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Artifacts directories and reports
18 | node_modules/
19 | build/
20 | src/npm-debug.log
21 | packages/**/.eslintcache
22 |
23 | # Temporary files and dirs (vim, etc.)
24 | *.swp
25 | *.tmp
26 | tmp_*
27 |
28 | # IDEs
29 | .idea/
30 | .vscode/
31 |
32 | .DS_Store
33 |
--------------------------------------------------------------------------------
/ui/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
2 | strict-peer-dependencies=false
3 |
--------------------------------------------------------------------------------
/ui/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/ui/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-standard-scss",
4 | "stylelint-prettier"
5 | ],
6 | "rules": {
7 | "selector-class-pattern": "^[a-z][a-zA-Z0-9]+$",
8 | "shorthand-property-no-redundant-values": null,
9 | "string-quotes": "single",
10 | "alpha-value-notation": "number"
11 | }
12 | }
--------------------------------------------------------------------------------
/ui/packages/ce/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules/**
2 | /.vscode/
3 | /.idea/
4 | /bin/
5 | /.git/
6 | **/build/**
7 | /ui/node_modules/
8 | /ui/packages/ce/node_modules/
9 | /ui/packages/shared/node_modules/
--------------------------------------------------------------------------------
/ui/packages/ce/.env:
--------------------------------------------------------------------------------
1 | PORT=3001
2 | # REACT_APP_API_URL_PREFIX
3 | # REACT_APP_WS_URL_PREFIX
4 |
--------------------------------------------------------------------------------
/ui/packages/ce/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "react-app",
4 | "rules": {
5 | "@typescript-eslint/no-explicit-any": "error"
6 | }
7 | }
--------------------------------------------------------------------------------
/ui/packages/ce/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
2 | strict-peer-dependencies=false
3 |
--------------------------------------------------------------------------------
/ui/packages/ce/Dockerfile:
--------------------------------------------------------------------------------
1 | # Biuld phase.
2 | FROM node:16.14-alpine as build
3 |
4 | WORKDIR /app
5 |
6 | COPY ./ui/ .
7 |
8 | # RUN --mount=type=bind,id=pnpm,source=.pnpm-store,target=/app/.pnpm-store
9 |
10 | ARG API_URL_PREFIX
11 | ENV REACT_APP_API_URL_PREFIX ${API_URL_PREFIX}
12 |
13 | ARG WS_URL_PREFIX
14 | ENV REACT_APP_WS_URL_PREFIX ${WS_URL_PREFIX}
15 |
16 | RUN apk add --no-cache --update git && \
17 | npm i -g pnpm@7.30.5; \
18 | pnpm config set store-dir /app/.pnpm-store; \
19 | pnpm set verify-store-integrity false; \
20 | pnpm --filter @postgres.ai/ce i; \
21 | pnpm --filter @postgres.ai/ce build
22 |
23 | # Run phase.
24 | FROM nginx:1.20.1-alpine as run
25 |
26 | COPY --from=build /app/packages/ce/build /srv/ce
27 | COPY ./ui/packages/ce/nginx.conf /etc/nginx/conf.d/ce.conf.template
28 | COPY ./ui/packages/ce/docker-entrypoint.sh /
29 |
30 | RUN rm -f /etc/nginx/conf.d/default.conf && chmod +x /docker-entrypoint.sh
31 |
32 | EXPOSE 2346
33 |
34 | ENTRYPOINT ["/docker-entrypoint.sh"]
35 |
36 | CMD ["nginx", "-g", "daemon off;"]
37 |
--------------------------------------------------------------------------------
/ui/packages/ce/ci_docker_build_push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | docker_file=${DOCKER_FILE:-""}
6 | tags=${TAGS:-""}
7 |
8 | registry_user=${REGISTRY_USER:-"${CI_REGISTRY_USER}"}
9 | registry_password=${REGISTRY_PASSWORD:-"${CI_REGISTRY_PASSWORD}"}
10 | registry=${REGISTRY:-"${CI_REGISTRY}"}
11 |
12 | echo "${registry_password}" | docker login --username $registry_user --password-stdin $registry
13 |
14 | tags_build=""
15 | tags_push=""
16 |
17 | IFS=',' read -ra ADDR string < /etc/nginx/conf.d/ce.conf
5 |
6 | exec "$@"
7 |
--------------------------------------------------------------------------------
/ui/packages/ce/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/postgres-ai/database-lab-engine/97b719af3ac1c500cbf2d1f9e266863fc15788ed/ui/packages/ce/public/favicon.ico
--------------------------------------------------------------------------------
/ui/packages/ce/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Database Lab CE",
3 | "name": "Database Lab Community Edition",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Auth/Card/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames'
2 |
3 | import styles from './styles.module.scss'
4 |
5 | type Props = {
6 | children: React.ReactNode
7 | className?: string
8 | onSubmit?: React.FormEventHandler
9 | }
10 |
11 | export const Card = (props: Props) => {
12 | return (
13 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Auth/Card/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 |
3 | @keyframes fade {
4 | from {
5 | opacity: 0;
6 | transform: translateY(8px);
7 | }
8 |
9 | to {
10 | opacity: 1;
11 | transform: none;
12 | }
13 | }
14 |
15 | .root {
16 | padding: 24px;
17 | border-radius: 8px;
18 | box-shadow: $box-shadow-layer;
19 | animation: fade 0.5s both ease-out;
20 | }
21 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Auth/styles.module.scss:
--------------------------------------------------------------------------------
1 | .content {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | flex: 1 1 100%;
6 | }
7 |
8 | .form {
9 | width: 320px;
10 | }
11 |
12 | .desc {
13 | margin-top: 12px;
14 | }
15 |
16 | .field {
17 | margin: 24px 0 0 0 !important;
18 | }
19 |
20 | .button {
21 | margin-top: 16px !important;
22 | }
23 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Instance/Clones/CreateClone/index.tsx:
--------------------------------------------------------------------------------
1 | import { CreateClone as CreateClonePage } from '@postgres.ai/shared/pages/CreateClone'
2 |
3 | import { PageContainer } from 'components/PageContainer'
4 | import { NavPath } from 'components/NavPath'
5 | import { ROUTES } from 'config/routes'
6 | import { getInstance } from 'api/instances/getInstance'
7 | import { getInstanceRetrieval } from 'api/instances/getInstanceRetrieval'
8 | import { getSnapshots } from 'api/snapshots/getSnapshots'
9 | import { createClone } from 'api/clones/createClone'
10 | import { getClone } from 'api/clones/getClone'
11 |
12 | export const CreateClone = () => {
13 | const routes = {
14 | clone: (cloneId: string) =>
15 | ROUTES.INSTANCE.CLONES.CLONE.createPath(cloneId),
16 | }
17 |
18 | const api = {
19 | getSnapshots,
20 | getInstance,
21 | getInstanceRetrieval,
22 | createClone,
23 | getClone,
24 | }
25 |
26 | const elements = {
27 | breadcrumbs: (
28 |
31 | ),
32 | }
33 |
34 | return (
35 |
36 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Instance/Clones/index.tsx:
--------------------------------------------------------------------------------
1 | import { Switch, Route, Redirect } from 'react-router-dom'
2 |
3 | import { ROUTES } from 'config/routes'
4 |
5 | import { CreateClone } from './CreateClone'
6 | import { Clone } from './Clone'
7 |
8 | export const Clones = () => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Instance/index.tsx:
--------------------------------------------------------------------------------
1 | import { Switch, Route, Redirect } from 'react-router-dom'
2 |
3 | import { ROUTES } from 'config/routes'
4 |
5 | import { Page } from './Page'
6 | import { Clones } from './Clones'
7 |
8 | export const Instance = () => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Layout/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './styles.module.scss'
2 | import { StickyTopBar } from 'App/Menu/StickyTopBar'
3 |
4 | type Props = {
5 | displayStickyBanner: boolean | undefined
6 | menu: React.ReactNode
7 | children: React.ReactNode
8 | }
9 |
10 | export const Layout = (props: Props) => {
11 | return (
12 |
13 | {props.displayStickyBanner &&
}
14 |
{props.menu}
15 |
16 | {props.children}
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Layout/styles.module.scss:
--------------------------------------------------------------------------------
1 | .root {
2 | flex: 0 0 100%;
3 | display: flex;
4 | min-height: 0;
5 | }
6 |
7 | .menu {
8 | flex: 0 0 auto;
9 | }
10 |
11 | .content {
12 | flex: 1 1 100%;
13 | display: flex;
14 | flex-direction: column;
15 | height: 100%;
16 | overflow: auto;
17 | }
18 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/Header/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .root {
5 | display: flex;
6 | flex-direction: column;
7 |
8 | @include touch-transition(padding);
9 |
10 | &:not(.collapsed) {
11 | padding-bottom: 16px;
12 |
13 | // border-bottom: 1px solid $color-gray;
14 | }
15 | }
16 |
17 | .header {
18 | display: flex;
19 | justify-content: flex-start;
20 | height: 32px;
21 | color: inherit;
22 | text-decoration: none;
23 |
24 | &.collapsed {
25 | justify-content: center;
26 | }
27 | }
28 |
29 | .logo {
30 | width: 36px;
31 | }
32 |
33 | .title {
34 | font-weight: 700;
35 | font-size: inherit;
36 | margin-left: 12px;
37 | white-space: nowrap;
38 | }
39 |
40 | .name {
41 | font-size: 14px;
42 | font-weight: 400;
43 | }
44 |
45 | .upgradeBtn {
46 | color: $color-orange;
47 | height: 24px;
48 | border: 1px solid $color-orange;
49 | justify-content: center;
50 | font-weight: 700;
51 | background-color: transparent;
52 | margin-top: 12px;
53 |
54 | &:hover {
55 | background-color: $color-orange;
56 | color: $color-white;
57 | }
58 | }
59 |
60 | .upgradeBtnIcon {
61 | height: 16px;
62 | width: 16px;
63 | margin-right: 6px;
64 | }
65 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/Instances/icons/plus.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/Instances/index.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from 'mobx-react-lite'
2 |
3 | import { ROUTES } from 'config/routes'
4 |
5 | import { ReactComponent as PlusIcon } from './icons/plus.svg'
6 | import { Button } from '@postgres.ai/shared/components/MenuButton'
7 |
8 | import styles from './styles.module.scss'
9 |
10 | type Props = {
11 | isCollapsed: boolean
12 | }
13 |
14 | export const Instances = observer((props: Props) => {
15 | return (
16 |
17 |
27 | }
32 | isCollapsed={props.isCollapsed}
33 | className={styles.addInstanceBtn}
34 | >
35 | Add instance
36 |
37 |
38 | )
39 | })
40 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/Instances/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .root {
5 | display: flex;
6 | flex-direction: column;
7 | flex: 1 1 auto;
8 | margin: 8px 0 48px 0;
9 | min-height: 0;
10 | }
11 |
12 | .links {
13 | flex: 0 1 auto;
14 | overflow: auto;
15 | }
16 |
17 | .link,
18 | .addInstanceBtn {
19 | border: 1px solid transparent;
20 | text-decoration: none;
21 |
22 | &.selected {
23 | border-color: $color-white;
24 | }
25 | }
26 |
27 | .link {
28 | justify-content: center;
29 | white-space: normal;
30 | }
31 |
32 | .addInstanceBtn {
33 | color: $color-gray;
34 | margin-top: 8px;
35 | flex: 0 0 auto;
36 |
37 | &:hover {
38 | color: $color-white;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/SignOutModal/index.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from '@postgres.ai/shared/components/Modal'
2 | import { Text } from '@postgres.ai/shared/components/Text'
3 | import { SimpleModalControls } from '@postgres.ai/shared/components/SimpleModalControls'
4 |
5 | export const SignOutModal = ({
6 | handleSignOut,
7 | onClose,
8 | isOpen,
9 | }: {
10 | handleSignOut: () => void
11 | onClose: () => void
12 | isOpen: boolean
13 | }) => {
14 | return (
15 |
16 |
17 | Are you sure you want to sign out? You will be redirected to the login
18 | page.
19 |
20 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/StickyTopBar/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .container {
5 | background-color: #fff2e5;
6 | position: fixed;
7 | top: 0;
8 | width: 100%;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | padding: 4px;
13 | font-size: 12px;
14 | z-index: 1000;
15 | flex-wrap: wrap;
16 | }
17 |
18 | .activateBtn {
19 | height: 19px;
20 | color: #fff;
21 | max-width: max-content;
22 | background-color: $color-orange;
23 |
24 | &:hover {
25 | color: #fff;
26 | background-color: $color-orange--hover;
27 | }
28 |
29 | &:disabled {
30 | cursor: not-allowed;
31 | background-color: #ccc;
32 | }
33 | }
34 |
35 | .spinner {
36 | margin-left: 8px;
37 | color: #fff;
38 | width: 12px !important;
39 | height: 12px !important;
40 | }
41 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/StickyTopBar/utils/index.ts:
--------------------------------------------------------------------------------
1 | export const capitalizeFirstLetter = (string: string) => {
2 | if (!string) return ''
3 |
4 | return string.charAt(0).toUpperCase() + string.slice(1) + '.'
5 | }
6 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/App/Menu/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .root {
5 | display: flex;
6 | flex-direction: column;
7 | padding: 28px 8px 8px 8px;
8 | justify-content: space-between;
9 | background: $color-gray-dark;
10 | height: 100%;
11 | width: 192px;
12 | color: $color-white;
13 | min-height: 0;
14 |
15 | @include touch-transition(width);
16 |
17 | @media screen and (max-width: '600px') {
18 | width: 100vw;
19 | }
20 |
21 | &.collapsed {
22 | width: 64px;
23 | }
24 | }
25 |
26 | .content {
27 | display: flex;
28 | flex-direction: column;
29 | flex: 1 1 auto;
30 | min-height: 0;
31 | }
32 |
33 | .footer {
34 | display: flex;
35 | flex-direction: column;
36 | }
37 |
38 | .supportBtn {
39 | background-color: $color-orange;
40 |
41 | &:hover {
42 | background-color: $color-orange--hover;
43 | }
44 | }
45 |
46 | .collapseBtn {
47 | background: transparent;
48 | font-weight: 400;
49 |
50 | &:hover {
51 | background: $color-gray-darkest;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/clones/createClone.ts:
--------------------------------------------------------------------------------
1 | import { CreateClone } from '@postgres.ai/shared/types/api/endpoints/createClone'
2 | import {
3 | CloneDto,
4 | formatCloneDto,
5 | } from '@postgres.ai/shared/types/api/entities/clone'
6 |
7 | import { request } from 'helpers/request'
8 |
9 | export const createClone: CreateClone = async (req) => {
10 | const response = await request('/clone', {
11 | method: 'POST',
12 | body: JSON.stringify({
13 | id: req.cloneId,
14 | snapshot: {
15 | id: req.snapshotId,
16 | },
17 | protected: req.isProtected,
18 | db: {
19 | username: req.dbUser,
20 | password: req.dbPassword,
21 | },
22 | }),
23 | })
24 |
25 | return {
26 | response: response.ok
27 | ? formatCloneDto((await response.json()) as CloneDto)
28 | : null,
29 | error: response.ok ? null : response,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/clones/destroyClone.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { DestroyClone } from '@postgres.ai/shared/types/api/endpoints/destroyClone'
9 |
10 | import { request } from 'helpers/request'
11 |
12 | export const destroyClone: DestroyClone = async (req) => {
13 | const response = await request(`/clone/${req.cloneId}`, {
14 | method: 'DELETE',
15 | })
16 |
17 | return {
18 | response: response.ok ? true : null,
19 | error: response.ok ? null : response,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/clones/getClone.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { GetClone } from '@postgres.ai/shared/types/api/endpoints/getClone'
9 | import {
10 | CloneDto,
11 | formatCloneDto,
12 | } from '@postgres.ai/shared/types/api/entities/clone'
13 |
14 | import { request } from 'helpers/request'
15 |
16 | export const getClone: GetClone = async (req) => {
17 | const response = await request(`/clone/${req.cloneId}`)
18 |
19 | return {
20 | response: response.ok
21 | ? formatCloneDto((await response.json()) as CloneDto)
22 | : null,
23 | error: response.ok ? null : response,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/clones/resetClone.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { ResetClone } from '@postgres.ai/shared/types/api/endpoints/resetClone'
9 |
10 | import { request } from 'helpers/request'
11 |
12 | export const resetClone: ResetClone = async (req) => {
13 | const response = await request(`/clone/${req.cloneId}/reset`, {
14 | method: 'POST',
15 | body: JSON.stringify({
16 | snapshotID: req.snapshotId,
17 | }),
18 | })
19 |
20 | return {
21 | response: response.ok ? true : null,
22 | error: response.ok ? null : response,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/clones/updateClone.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { UpdateClone } from '@postgres.ai/shared/types/api/endpoints/updateClone'
9 |
10 | import { request } from 'helpers/request'
11 |
12 | export const updateClone: UpdateClone = async (req) => {
13 | const response = await request(`/clone/${req.cloneId}`, {
14 | method: 'PATCH',
15 | body: JSON.stringify({
16 | protected: req.clone.isProtected,
17 | }),
18 | })
19 |
20 | return {
21 | response: response.ok ? true : null,
22 | error: response.ok ? null : response,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/configs/activateBilling.ts:
--------------------------------------------------------------------------------
1 | import { request } from 'helpers/request'
2 |
3 | export const activateBilling = async () => {
4 | const response = await request('/admin/activate', {
5 | method: 'POST',
6 | })
7 |
8 | return {
9 | response: response.ok ? await response.json() : null,
10 | error: response.ok ? null : await response.json(),
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/configs/getBillingStatus.ts:
--------------------------------------------------------------------------------
1 | import { request } from 'helpers/request'
2 |
3 | export type ResponseType = {
4 | result: string
5 | billing_active: boolean
6 | recognized_org: {
7 | id: string
8 | name: string
9 | alias: string
10 | billing_page: string
11 | priveleged_until: Date
12 | }
13 | }
14 |
15 | export const getBillingStatus = async () => {
16 | const response = await request('/admin/billing-status')
17 |
18 | return {
19 | response: response.ok ? ((await response.json()) as ResponseType) : null,
20 | error: response.ok ? null : await response.json(),
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/configs/getConfig.ts:
--------------------------------------------------------------------------------
1 | import { formatConfig } from '@postgres.ai/shared/types/api/entities/config'
2 | import { request } from 'helpers/request'
3 |
4 | export const getConfig = async () => {
5 | const response = await request('/admin/config')
6 |
7 | return {
8 | response: response.ok ? formatConfig(await response.json()) : null,
9 | error: response.ok ? null : response,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/configs/getFullConfig.ts:
--------------------------------------------------------------------------------
1 | import { request } from 'helpers/request'
2 | export const getFullConfig = async () => {
3 | const response = await request('/admin/config.yaml')
4 | .then((res) => res.blob())
5 | .then((blob) => blob.text())
6 | .then((yamlAsString) => {
7 | return yamlAsString
8 | })
9 |
10 | return {
11 | response: response ? response : null,
12 | error: response && null,
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/configs/getSeImages.ts:
--------------------------------------------------------------------------------
1 | import { request } from 'helpers/request'
2 |
3 | export const getSeImages = async ({
4 | packageGroup,
5 | platformUrl,
6 | }: {
7 | packageGroup: string
8 | platformUrl?: string
9 | }) => {
10 | const response = await request(
11 | `/dblab_se_images?package_group=eq.${packageGroup}
12 | `,
13 | {},
14 | platformUrl,
15 | )
16 |
17 | return {
18 | response: response.ok ? await response.json() : null,
19 | error: response.ok ? null : response,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/configs/testDbSource.ts:
--------------------------------------------------------------------------------
1 | import { dbSource } from '@postgres.ai/shared/types/api/entities/dbSource'
2 | import { request } from 'helpers/request'
3 |
4 | export const testDbSource = async (req: dbSource) => {
5 | const response = await request('/admin/test-db-source', {
6 | method: 'POST',
7 | body: JSON.stringify({
8 | host: req.host,
9 | port: req.port.toString(),
10 | dbname: req.dbname,
11 | username: req.username,
12 | password: req.password,
13 | db_list: req.db_list
14 | }),
15 | })
16 |
17 | return {
18 | response: response.ok ? await response.json(): null,
19 | error: response.ok ? null : await response.json()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/engine/getEngine.ts:
--------------------------------------------------------------------------------
1 | import {
2 | EngineDto,
3 | formatEngineDto,
4 | } from '@postgres.ai/shared/types/api/endpoints/getEngine'
5 | import { request } from 'helpers/request'
6 |
7 | export const getEngine = async () => {
8 | const response = await request('/healthz')
9 |
10 | return {
11 | response: response.ok
12 | ? formatEngineDto((await response.json()) as EngineDto)
13 | : null,
14 | error: response.ok ? null : response,
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/engine/getWSToken.ts:
--------------------------------------------------------------------------------
1 | import { request } from 'helpers/request'
2 | import { formatWSTokenDto, WSTokenDTO } from '@postgres.ai/shared/types/api/entities/wsToken'
3 | import { GetWSToken } from "@postgres.ai/shared/types/api/endpoints/getWSToken";
4 |
5 | export const getWSToken: GetWSToken = async (req ) => {
6 | const response = await request('/admin/ws-auth')
7 |
8 | return {
9 | response: response.ok
10 | ? formatWSTokenDto((await response.json()) as WSTokenDTO)
11 | : null,
12 | error: response.ok ? null : response,
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/engine/initWS.ts:
--------------------------------------------------------------------------------
1 | import { InitWS } from "@postgres.ai/shared/types/api/endpoints/initWS";
2 | import { WS_URL_PREFIX } from 'config/env'
3 |
4 | export const initWS: InitWS = (path: string, token: string): WebSocket => {
5 | let url = new URL(WS_URL_PREFIX + path, window.location.href);
6 | url.protocol = url.protocol.replace('http', 'ws');
7 | const wsAddr = url.href + '?token=' + token;
8 |
9 | return new WebSocket(wsAddr)
10 | }
11 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/instances/getInstance.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { GetInstance } from '@postgres.ai/shared/types/api/endpoints/getInstance'
9 | import { formatInstanceDto, InstanceDto } from '@postgres.ai/shared/types/api/entities/instance'
10 | import { InstanceStateDto } from '@postgres.ai/shared/types/api/entities/instanceState'
11 |
12 | import { request } from 'helpers/request'
13 |
14 | export const getInstance: GetInstance = async () => {
15 | const response = await request('/status')
16 |
17 | // Hack to get capability with platform API.
18 | const responseDto = response.ok
19 | ? {
20 | // Fake id which means nothing.
21 | id: 0,
22 | state: (await response.json()) as InstanceStateDto,
23 | }
24 | : null
25 |
26 | return {
27 | response: responseDto ? formatInstanceDto(responseDto as InstanceDto) : null,
28 | error: response.ok ? null : response,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/instances/getInstanceRetrieval.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2022, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { request } from 'helpers/request'
9 | import { formatInstanceRetrieval } from '@postgres.ai/shared/types/api/entities/instanceRetrieval'
10 |
11 | export const getInstanceRetrieval = async () => {
12 | const response = await request('/instance/retrieval')
13 |
14 | return {
15 | response: response.ok ? formatInstanceRetrieval(await response.json()) : null,
16 | error: response.ok ? null : response,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/api/snapshots/getSnapshots.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 | import { GetSnapshots } from '@postgres.ai/shared/types/api/endpoints/getSnapshots'
8 | import {
9 | SnapshotDto,
10 | formatSnapshotDto,
11 | } from '@postgres.ai/shared/types/api/entities/snapshot'
12 |
13 | import { request } from 'helpers/request'
14 |
15 | export const getSnapshots: GetSnapshots = async (req) => {
16 | const response = await request('/snapshots')
17 |
18 | return {
19 | response: response.ok
20 | ? ((await response.json()) as SnapshotDto[]).map(formatSnapshotDto)
21 | : null,
22 | error: response.ok ? null : response,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/components/NavPath/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { NavLink } from 'react-router-dom'
3 | import cn from 'classnames'
4 |
5 | import styles from './styles.module.scss'
6 |
7 | export type NavRoute = {
8 | name: string
9 | path: string
10 | }
11 |
12 | type Props = {
13 | routes: NavRoute[]
14 | className?: string
15 | }
16 |
17 | export const NavPath = (props: Props) => {
18 | return (
19 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/components/NavPath/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .root {
5 | display: flex;
6 | padding-bottom: 8px;
7 | border-bottom: 1px solid #ccd7da;
8 | color: $color-gray-semi-dark;
9 | font-size: 12px;
10 | font-weight: 500;
11 | }
12 |
13 | .link {
14 | color: inherit;
15 | text-decoration: none;
16 |
17 | &:hover {
18 | color: $color-blue-dark;
19 | }
20 |
21 | &.active {
22 | color: $color-black;
23 | }
24 | }
25 |
26 | .divider {
27 | margin: 0 4px;
28 | }
29 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/components/PageContainer/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames'
2 |
3 | import styles from './styles.module.scss'
4 |
5 | type Props = {
6 | children: React.ReactNode
7 | className?: string
8 | }
9 |
10 | export const PageContainer = (props: Props) => {
11 | return (
12 | {props.children}
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/components/PageContainer/styles.module.scss:
--------------------------------------------------------------------------------
1 | .root {
2 | padding: 28px 24px;
3 | flex: 1 1 100%;
4 | display: flex;
5 | flex-direction: column;
6 |
7 | @media screen and (max-width: '600px') {
8 | padding: 24px 12px 24px 12px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/config/env.ts:
--------------------------------------------------------------------------------
1 | export const NODE_ENV = process.env.NODE_ENV
2 | export const API_URL_PREFIX = process.env.REACT_APP_API_URL_PREFIX ?? ''
3 | export const WS_URL_PREFIX = process.env.REACT_APP_WS_URL_PREFIX ?? ''
4 | export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP
5 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/config/routes.tsx:
--------------------------------------------------------------------------------
1 | export const ROUTES = {
2 | name: 'Database Lab',
3 | path: '/',
4 |
5 | AUTH: {
6 | name: 'Auth',
7 | path: '/auth',
8 | },
9 |
10 | INSTANCE: {
11 | path: `/instance`,
12 | name: 'Instance',
13 |
14 | CLONES: {
15 | path: `/instance/clones`,
16 |
17 | CREATE: {
18 | name: 'Create clone',
19 | path: `/instance/clones/create`,
20 | },
21 |
22 | CLONE: {
23 | name: 'Clone',
24 | createPath: (cloneId = ':cloneId') => `/instance/clones/${cloneId}`,
25 | },
26 | },
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/helpers/edition.ts:
--------------------------------------------------------------------------------
1 | import { appStore } from "stores/app";
2 |
3 | const communityEdition = 'Community Edition'
4 | const standardEdition = 'Standard Edition'
5 |
6 | export const DLEEdition = (): string => {
7 | switch (appStore.engine?.data?.edition) {
8 | case 'standard':
9 | return standardEdition
10 |
11 | case 'community':
12 | return communityEdition
13 |
14 | default:
15 | return communityEdition
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/helpers/localStorage.ts:
--------------------------------------------------------------------------------
1 | import { LocalStorage as LocalStorageShared } from '@postgres.ai/shared/helpers/localStorage'
2 |
3 | class LocalStorage extends LocalStorageShared {}
4 |
5 | export const localStorage = new LocalStorage()
6 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/helpers/request.ts:
--------------------------------------------------------------------------------
1 | import {
2 | request as requestCore,
3 | RequestOptions,
4 | } from '@postgres.ai/shared/helpers/request'
5 |
6 | import { localStorage } from 'helpers/localStorage'
7 | import { appStore } from 'stores/app'
8 | import { API_URL_PREFIX } from 'config/env'
9 |
10 | export const request = async (
11 | path: string,
12 | options?: RequestOptions,
13 | customPrefix?: string,
14 | ) => {
15 | const authToken = localStorage.getAuthToken()
16 |
17 | const response = await requestCore(
18 | `${customPrefix ? customPrefix?.replace(/"/g, '') : API_URL_PREFIX}${path}`,
19 | {
20 | ...options,
21 | headers: {
22 | ...(authToken && { 'Verification-Token': authToken }),
23 | ...options?.headers,
24 | },
25 | },
26 | )
27 |
28 | if (response.status === 401) {
29 | appStore.setIsInvalidAuthToken()
30 | localStorage.removeAuthToken()
31 | } else {
32 | appStore.setIsValidAuthToken()
33 | }
34 |
35 | return response
36 | }
37 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/index.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/global';
2 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { ThemeProvider } from '@material-ui/core'
4 |
5 | import { theme } from '@postgres.ai/shared/styles/theme'
6 |
7 | import './index.scss'
8 |
9 | import { App } from './App'
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 | ,
17 | document.getElementById('root'),
18 | )
19 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // Env types.
4 | declare namespace NodeJS {
5 | interface ProcessEnv {
6 | readonly REACT_APP_API_URL_PREFIX?: string
7 | readonly REACT_APP_WS_URL_PREFIX?: string
8 | readonly BUILD_TIMESTAMP: number
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/packages/ce/src/stores/app.ts:
--------------------------------------------------------------------------------
1 | import { makeAutoObservable } from 'mobx'
2 |
3 | import { getEngine } from 'api/engine/getEngine'
4 | import { EngineType } from '@postgres.ai/shared/types/api/endpoints/getEngine'
5 |
6 | type EngineProp = {
7 | data: EngineType | null | undefined
8 | isLoading: boolean
9 | }
10 |
11 | class AppStore {
12 | readonly engine: EngineProp = {
13 | data: undefined,
14 | isLoading: false,
15 | }
16 |
17 | isValidAuthToken: boolean | undefined = undefined
18 |
19 | constructor() {
20 | makeAutoObservable(this)
21 | }
22 |
23 | loadData = async () => {
24 | this.engine.isLoading = true
25 | const { response } = await getEngine()
26 | this.engine.data = response
27 | this.engine.isLoading = false
28 | }
29 |
30 | setIsValidAuthToken = () => (this.isValidAuthToken = true)
31 |
32 | setIsInvalidAuthToken = () => (this.isValidAuthToken = false)
33 | }
34 |
35 | export const appStore = new AppStore()
36 |
--------------------------------------------------------------------------------
/ui/packages/ce/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": "src",
19 | "noImplicitAny": true
20 | },
21 | "include": ["src", "../shared"]
22 | }
23 |
--------------------------------------------------------------------------------
/ui/packages/shared/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
2 | strict-peer-dependencies=false
3 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/AlertSnackbar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Snackbar from '@mui/material/Snackbar';
3 | import Alert from '@mui/material/Alert';
4 | import {useAlertSnackbar} from "./useAlertSnackbar";
5 |
6 | export const AlertSnackbar = () => {
7 | const { snackbarMessage, closeSnackbar } = useAlertSnackbar();
8 | return (
9 |
13 |
19 | {snackbarMessage?.message}
20 |
21 |
22 | )
23 | }
--------------------------------------------------------------------------------
/ui/packages/shared/components/Button2/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import cn from 'classnames'
3 |
4 | import { Spinner } from '@postgres.ai/shared/components/Spinner'
5 |
6 | import styles from './styles.module.scss'
7 |
8 | type Props = {
9 | type?: React.ButtonHTMLAttributes['type']
10 | children: React.ReactNode
11 | onClick?: React.DOMAttributes['onClick']
12 | size?: 'sm' | 'md' | 'lg'
13 | theme?: 'primary' | 'secondary' | 'accent'
14 | className?: string
15 | isDisabled?: boolean
16 | isLoading?: boolean
17 | }
18 |
19 | export const Button = React.forwardRef(
20 | (props, ref) => {
21 | const { type = 'button', size = 'md', theme = 'secondary' } = props
22 |
23 | const isDisabled = props.isDisabled || props.isLoading
24 |
25 | return (
26 |
41 | )
42 | },
43 | )
44 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/FormattedText/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .root {
5 | position: relative;
6 | background: $color-white--100;
7 | border: 1px solid $color-gray-100;
8 | border-radius: $border-radius--small;
9 | overflow: hidden;
10 |
11 | &:hover .copyButtonContainer {
12 | opacity: 1;
13 | }
14 | }
15 |
16 | .content {
17 | padding: 10px 80px 10px 10px;
18 | line-height: 1.5;
19 | overflow: auto;
20 | max-height: inherit;
21 | margin: 0;
22 | }
23 |
24 | .copyButtonContainer {
25 | opacity: 0;
26 |
27 | @include touch-transition(opacity);
28 | }
29 |
30 | .copyButton {
31 | position: absolute;
32 | top: 8.5px;
33 | right: 8.5px;
34 | }
35 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/GatewayLink/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import cn from 'classnames'
9 |
10 | import styles from '@postgres.ai/shared/components/Link2/styles.module.scss'
11 |
12 | type Props = React.DetailedHTMLProps<
13 | React.AnchorHTMLAttributes,
14 | HTMLAnchorElement
15 | >
16 |
17 | export const GatewayLink = (props: Props) => {
18 | const {
19 | rel = 'noopener noreferrer',
20 | target = '_blank',
21 | className,
22 | ...otherProps
23 | } = props
24 |
25 | return (
26 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/HorizontalScrollContainer/types.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | export type Dimensions = {
9 | offsetWidth: number;
10 | scrollWidth: number;
11 | scrollLeft: number;
12 | };
13 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/HorizontalScrollContainer/utils.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { Dimensions } from './types';
9 |
10 | export const checkIsVisibleLeftCurtain = (dimensions: Dimensions) => {
11 | return dimensions.scrollLeft > 0;
12 | };
13 |
14 | export const checkIsVisibleRightCurtain = (dimensions: Dimensions) => {
15 | return dimensions.scrollLeft < dimensions.scrollWidth - dimensions.offsetWidth;
16 | };
17 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/ImportantText/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react'
9 | import { makeStyles } from '@material-ui/core'
10 |
11 | type Props = {
12 | children: React.ReactNode
13 | }
14 |
15 | const useStyles = makeStyles(
16 | {
17 | root: {
18 | fontWeight: 'bold',
19 | whiteSpace: 'nowrap',
20 | },
21 | },
22 | { index: 1 },
23 | )
24 |
25 | export const ImportantText = (props: Props) => {
26 | const classes = useStyles()
27 |
28 | return {props.children}
29 | }
30 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/Link2/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { Link as LinkBase, LinkProps } from 'react-router-dom'
9 | import cn from 'classnames'
10 |
11 | import styles from './styles.module.scss'
12 |
13 | type Props = LinkProps & { external?: boolean }
14 |
15 | export const Link = (props: Props) => {
16 | const { className, external, to, ...otherProps } = props
17 |
18 | if (external) {
19 | return (
20 |
27 | )
28 | }
29 |
30 | return
31 | }
32 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/Link2/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .root {
5 | color: $color-blue-main;
6 |
7 | @include touch-transition(color);
8 |
9 | &:hover {
10 | color: $color-blue-dark;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/MenuButton/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 | @import '@postgres.ai/shared/styles/mixins';
3 |
4 | .root {
5 | display: flex;
6 | align-items: center;
7 | padding: 0 10px;
8 | background-color: $color-gray-darkest;
9 | cursor: pointer;
10 | border-radius: 4px;
11 | border: 0;
12 | height: 48px;
13 | width: 100%;
14 | color: inherit;
15 | white-space: nowrap;
16 | overflow: hidden;
17 | font-weight: 700;
18 | text-decoration: none;
19 |
20 | @include touch-transition(background-color color);
21 |
22 | &:hover {
23 | background-color: $color-gray-darkest--hover;
24 | color: inherit;
25 | }
26 |
27 | &.collapsed {
28 | justify-content: center;
29 | }
30 |
31 | + .root {
32 | margin-top: 8px;
33 | }
34 | }
35 |
36 | .icon {
37 | font-size: 0;
38 |
39 | &:not(.collapsed) {
40 | margin-right: 8px;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/PageSpinner/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { Spinner } from '@postgres.ai/shared/components/Spinner'
9 |
10 | import styles from './styles.module.scss'
11 |
12 | export const PageSpinner = () => {
13 | return (
14 |
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/PageSpinner/styles.module.scss:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | .root {
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | flex: 1 1 100%;
13 | }
14 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/Spinner/icon.tsx:
--------------------------------------------------------------------------------
1 | export const SpinnerIcon = ({ className }: { className: string }) => (
2 |
29 | )
30 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/Spinner/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames'
2 | import { SpinnerIcon } from './icon'
3 |
4 | import styles from './styles.module.scss'
5 |
6 | export type Size = 'sm' | 'md' | 'lg'
7 |
8 | export type Props = {
9 | size?: Size
10 | className?: string
11 | }
12 |
13 | export const Spinner = (props: Props) => {
14 | const { size = 'md' } = props
15 | return
16 | }
17 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/Spinner/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 |
3 | @keyframes rotation {
4 | from {
5 | transform: rotate(0);
6 | }
7 |
8 | to {
9 | transform: rotate(360deg);
10 | }
11 | }
12 |
13 | .root {
14 | color: $color-orange;
15 |
16 | animation: rotation ease-in-out .5s infinite;
17 |
18 | // Sizes.
19 | &.sm {
20 | height: 16px;
21 | width: 16px;
22 | }
23 |
24 | &.md {
25 | height: 24px;
26 | width: 24px;
27 | }
28 |
29 | &.lg {
30 | height: 32px;
31 | width: 32px;
32 | }
33 | }
--------------------------------------------------------------------------------
/ui/packages/shared/components/Status/styles.module.scss:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | @import '@postgres.ai/shared/styles/vars';
9 |
10 | .root {
11 | &.ok {
12 | color: $color-status-ok;
13 | }
14 |
15 | &.warning {
16 | color: $color-status-warning;
17 | }
18 |
19 | &.error {
20 | color: $color-status-error;
21 | }
22 |
23 | &.waiting {
24 | color: $color-status-waiting;
25 | }
26 |
27 | &.unknown {
28 | color: $color-status-unknown;
29 | }
30 | }
31 |
32 | .iconContainer {
33 | display: inline-block;
34 | margin-right: 0.5em;
35 | width: 0.8em;
36 | position: relative;
37 | }
38 |
39 | .icon {
40 | position: absolute;
41 | top: 0;
42 | left: 0;
43 | width: 100%;
44 | height: 100%;
45 | }
46 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/StubContainer/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react'
9 | import { makeStyles } from '@material-ui/core'
10 | import Box from '@mui/material/Box'
11 | import clsx from 'clsx'
12 |
13 | const useStyles = makeStyles(
14 | {
15 | root: {
16 | padding: '20px 0',
17 | flex: '1 1 100%',
18 | },
19 | },
20 | { index: 1 },
21 | )
22 |
23 | type Props = {
24 | className?: string
25 | children: React.ReactNode
26 | }
27 |
28 | export const StubContainer = (props: Props) => {
29 | const classes = useStyles()
30 |
31 | return (
32 |
38 | {props.children}
39 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/StubSpinnerFlex/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames'
2 |
3 | import { Spinner, Size } from '@postgres.ai/shared/components/Spinner'
4 |
5 | import styles from './styles.module.scss'
6 |
7 | type Props = {
8 | mode?: 'absolute' | 'flex'
9 | size?: Size
10 | className?: string
11 | }
12 |
13 | export const StubSpinner = (props: Props) => {
14 | const { mode = 'flex', size = 'lg' } = props
15 | return (
16 |
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/StubSpinnerFlex/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 |
3 | .root {
4 | background: $color-white;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | width: 100%;
9 | height: 100%;
10 |
11 | &.absolute {
12 | position: absolute;
13 | top: 0;
14 | left: 0;
15 | }
16 |
17 | &.flex {
18 | flex: 1 1 100%;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ui/packages/shared/components/Text/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react'
9 | import { makeStyles } from '@material-ui/core'
10 |
11 | type Props = {
12 | children: React.ReactNode
13 | }
14 |
15 | const useStyles = makeStyles(
16 | {
17 | root: {
18 | margin: 0,
19 | },
20 | },
21 | { index: 1 },
22 | )
23 |
24 | export const Text = (props: Props) => {
25 | const classes = useStyles()
26 |
27 | return {props.children}
28 | }
29 |
--------------------------------------------------------------------------------
/ui/packages/shared/config/index.ts:
--------------------------------------------------------------------------------
1 | import { Locale } from 'date-fns'
2 | import { enUS } from 'date-fns/locale'
3 | import { getUserLocale } from 'get-user-locale'
4 |
5 | type Config = {
6 | dateFnsLocale: Locale
7 | appName: string
8 | }
9 |
10 | export const config: Config = {
11 | dateFnsLocale: enUS,
12 | appName: 'Postgres.ai',
13 | }
14 |
15 | const loadDateFnsLocale = async () => {
16 | const userLocale = getUserLocale()
17 |
18 | // We are already using this locale.
19 | if (userLocale === config.dateFnsLocale.code) return
20 |
21 | try {
22 | const locale = await import(`date-fns/locale/${userLocale}`)
23 | config.dateFnsLocale = locale.default
24 | return
25 | } catch (e) {
26 | // Unavailable locale.
27 | }
28 | }
29 |
30 | export const initConfig = async () => {
31 | await loadDateFnsLocale()
32 | }
33 |
--------------------------------------------------------------------------------
/ui/packages/shared/config/links.ts:
--------------------------------------------------------------------------------
1 | export const linksConfig = {
2 | cloudSignIn: 'https://postgres.ai/pricing',
3 | docs: 'https://postgres.ai/docs',
4 | support: 'https://postgres.ai/contact',
5 | github: 'https://github.com/postgres-ai/database-lab-engine',
6 | }
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/helpers/localStorage.ts:
--------------------------------------------------------------------------------
1 | const keys = {
2 | // Keep this name.
3 | authToken: 'token',
4 | }
5 |
6 | export class LocalStorage {
7 | // Auth token.
8 | getAuthToken = () => window.localStorage.getItem(keys.authToken)
9 |
10 | setAuthToken = (value: string) => window.localStorage.setItem(keys.authToken, value)
11 |
12 | removeAuthToken = () => window.localStorage.removeItem(keys.authToken)
13 | }
14 |
15 | export const localStorage = new LocalStorage()
16 |
--------------------------------------------------------------------------------
/ui/packages/shared/helpers/request.ts:
--------------------------------------------------------------------------------
1 | import 'whatwg-fetch'
2 |
3 | type RequestParams = Record
4 |
5 | export type RequestOptions = RequestInit & {
6 | params?: RequestParams
7 | }
8 |
9 | const serializeParams = (params: RequestParams | null) => {
10 | if (!params) return null
11 |
12 | const searchParams = new URLSearchParams()
13 |
14 | Object.entries(params).map((param) => {
15 | const [key, value] = param
16 | searchParams.append(key, String(value))
17 | })
18 |
19 | return searchParams.toString()
20 | }
21 |
22 | const createUrl = (path: string, params: RequestParams | null) => {
23 | const serializedParams = serializeParams(params)
24 | const queryString = serializedParams ? `?${serializedParams}` : ''
25 | return `${path}${queryString}`
26 | }
27 |
28 | export const request = async (path: string, options?: RequestOptions) => {
29 | const { params = null, ...requestInit } = options ?? {}
30 |
31 | const url = createUrl(path, params)
32 |
33 | try {
34 | return await window.fetch(url, {
35 | ...requestInit,
36 | headers: {
37 | 'Content-Type': 'application/json',
38 | ...requestInit?.headers,
39 | },
40 | })
41 | } catch (e) {
42 | return new Response(null, {
43 | status: 500,
44 | statusText: `Unknown error`,
45 | })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/ui/packages/shared/hooks/useWindowDimensions.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export const useWindowDimensions = () => {
4 | const [windowDimensions, setWindowDimensions] = useState(window.innerWidth)
5 |
6 | useEffect(() => {
7 | const handleResize = () => {
8 | setWindowDimensions(window.innerWidth)
9 | }
10 |
11 | window.addEventListener('resize', handleResize)
12 | return () => window.removeEventListener('resize', handleResize)
13 | }, [])
14 |
15 | return windowDimensions
16 | }
17 |
--------------------------------------------------------------------------------
/ui/packages/shared/icons/ArrowDropDown/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react'
9 |
10 | type Props = {
11 | className?: string
12 | }
13 |
14 | export const ArrowDropDownIcon = (props: Props) => {
15 | return (
16 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/ui/packages/shared/icons/Circle/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react'
9 |
10 | type Props = {
11 | className?: string
12 | }
13 |
14 | export const CircleIcon = (props: Props) => {
15 | const { className } = props
16 |
17 | return (
18 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/ui/packages/shared/icons/External/index.tsx:
--------------------------------------------------------------------------------
1 | export const ExternalIcon = ({ className }: { className: string }) => (
2 |
14 | )
15 |
--------------------------------------------------------------------------------
/ui/packages/shared/icons/Info/index.tsx:
--------------------------------------------------------------------------------
1 | export const InfoIcon = ({ className }: { className?: string }) => (
2 |
12 | )
13 |
--------------------------------------------------------------------------------
/ui/packages/shared/icons/Shield/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react'
9 |
10 | type Props = {
11 | className?: string
12 | }
13 |
14 | export const ShieldIcon = React.forwardRef(
15 | (props, ref) => {
16 | const { className, ...hiddenProps } = props
17 | return (
18 |
31 | )
32 | },
33 | )
34 |
--------------------------------------------------------------------------------
/ui/packages/shared/meta.json:
--------------------------------------------------------------------------------
1 | {"buildDate":1634829055606}
2 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Clone/context.ts:
--------------------------------------------------------------------------------
1 | import { createStrictContext } from '@postgres.ai/shared/utils/react'
2 |
3 | import { Api } from './stores/Main'
4 | import { Stores } from './useCreatedStores'
5 |
6 | export type Host = {
7 | instanceId: string
8 | cloneId: string
9 | routes: {
10 | instance: () => string
11 | }
12 | api: Api
13 | elements: {
14 | breadcrumbs: React.ReactNode
15 | }
16 | }
17 |
18 | export const { useStrictContext: useHost, Provider: HostProvider } =
19 | createStrictContext()
20 |
21 | export const { useStrictContext: useStores, Provider: StoresProvider } =
22 | createStrictContext()
23 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Clone/useCreatedStores.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 |
3 | import { MainStore } from './stores/Main'
4 |
5 | import { Host } from './context'
6 |
7 | export const useCreatedStores = (host: Host) => ({
8 | main: useMemo(() => new MainStore(host.api), []),
9 | })
10 |
11 | export type Stores = ReturnType
12 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/CreateClone/styles.module.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | margin-top: 16px;
3 | }
4 |
5 | .form {
6 | max-width: 425px;
7 | margin-top: 8px;
8 | }
9 |
10 | .section {
11 | & + .section {
12 | margin-top: 32px;
13 | }
14 | }
15 |
16 | .title {
17 | font-size: inherit;
18 | font-weight: 700;
19 | }
20 |
21 | .text {
22 | margin-top: 8px;
23 | }
24 |
25 | .remark {
26 | font-size: 12px;
27 | }
28 |
29 | .error {
30 | color: red;
31 | }
32 |
33 | .snapshotTag {
34 | font-weight: 700;
35 | margin-left: 4px;
36 | }
37 |
38 | .summary {
39 | display: flex;
40 | align-items: center;
41 | background-color: rgba(#808080, 0.15) !important;
42 | padding: 12px;
43 | }
44 |
45 | .summaryIcon {
46 | width: 30px;
47 | color: silver;
48 | margin-right: 12px;
49 | }
50 |
51 | .params {
52 | flex: 1 1 100%;
53 | }
54 |
55 | .param {
56 | display: flex;
57 | justify-content: space-between;
58 | }
59 |
60 | .controls {
61 | display: flex;
62 | align-items: center;
63 | }
64 |
65 | .spinner {
66 | margin-left: 8px;
67 | }
68 |
69 | .elapsedTime {
70 | margin-left: 24px;
71 | }
72 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/CreateClone/useCreatedStores.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 |
3 | import { MainStore, MainStoreApi } from './stores/Main'
4 |
5 | export const useCreatedStores = (api: MainStoreApi) => ({
6 | main: useMemo(() => new MainStore(api), []),
7 | })
8 |
9 | export type Stores = ReturnType
10 |
11 | export type { MainStoreApi }
12 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/CreateClone/useForm.ts:
--------------------------------------------------------------------------------
1 | import { useFormik } from 'formik'
2 | import * as Yup from 'yup'
3 |
4 | export type FormValues = {
5 | cloneId: string
6 | snapshotId: string
7 | dbUser: string
8 | dbPassword: string
9 | isProtected: boolean
10 | }
11 |
12 | const Schema = Yup.object().shape({
13 | cloneId: Yup.string(),
14 | snapshotId: Yup.string().required('Date state time is required'),
15 | dbUser: Yup.string().required('Database username is required'),
16 | dbPassword: Yup.string().required('Database password is required'),
17 | isProtected: Yup.boolean(),
18 | })
19 |
20 | export const useForm = (onSubmit: (values: FormValues) => void) => {
21 | const formik = useFormik({
22 | initialValues: {
23 | cloneId: '',
24 | snapshotId: '',
25 | dbUser: '',
26 | dbPassword: '',
27 | isProtected: false,
28 | },
29 | validationSchema: Schema,
30 | onSubmit,
31 | validateOnBlur: false,
32 | validateOnChange: false,
33 | })
34 |
35 | return formik
36 | }
37 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Clones/Header/Item/index.tsx:
--------------------------------------------------------------------------------
1 | import styles from './styles.module.scss'
2 |
3 | type Props = {
4 | value: React.ReactNode
5 | children: React.ReactNode
6 | }
7 |
8 | export const Item = (props: Props) => {
9 | return (
10 |
11 |
{props.value}
12 |
{props.children}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Clones/Header/Item/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 |
3 | .root {
4 | font-size: 12px;
5 | text-align: center;
6 | color: $color-gray-semi-dark;
7 | }
8 |
9 | .value {
10 | font-weight: bold;
11 | font-size: 14px;
12 | color: $color-black;
13 | }
14 |
15 | .description {
16 | margin-top: 4px;
17 | }
18 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Clones/Header/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/mixins';
2 |
3 | .root {
4 | display: flex;
5 | justify-content: space-between;
6 | padding: 20px;
7 |
8 | @include sm {
9 | padding: 20px 0;
10 | }
11 | }
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/ClonesModal/utils.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | export const getTags = ({
9 | pool,
10 | snapshotId,
11 | }: {
12 | pool: string | null
13 | snapshotId: string | null
14 | }) => {
15 | const tags = []
16 |
17 | if (pool) tags.push({ name: 'Disk', value: pool })
18 | if (snapshotId) tags.push({ name: 'Snapshot', value: snapshotId })
19 |
20 | return tags
21 | }
22 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/InactiveInstance/utils.ts:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 |
3 | export const formatTimestampUtc = (timestamp: moment.MomentInput) => {
4 | if (!timestamp) {
5 | return null
6 | }
7 |
8 | return moment(timestamp).utc().format('YYYY-MM-DD HH:mm:ss UTC')
9 | }
10 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Connection/ConnectModal/Content/utils.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { Instance } from '@postgres.ai/shared/types/api/entities/instance'
9 |
10 | export const getCliInitCommand = (instance: Instance) =>
11 | `dblab init --url ${instance.url} --token TOKEN --environment-id ${instance.projectName}`
12 |
13 | export const getSshPortForwardingCommand = (instance: Instance) => {
14 | if (instance.sshServerUrl) {
15 | // Parse the URL to get the port
16 | const url = new URL(instance.url as string)
17 | const port = url.port || '2345'
18 | // Here we hard-code the API port on the server (2345)- this is a requirement now
19 | // for all DBLab instances working via tunnel, per decision made (NIkolayS 2024-05-22)
20 | return `ssh -NTML ${port}:localhost:2345 ${instance.sshServerUrl} -i ~/.ssh/id_rsa`
21 | } else {
22 | return null
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Connection/ConnectModal/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { useState } from 'react'
9 |
10 | import { Button } from '@postgres.ai/shared/components/Button2'
11 | import { Modal } from '@postgres.ai/shared/components/Modal'
12 |
13 | import { Content } from './Content'
14 |
15 | type Props = {
16 | className?: string
17 | }
18 |
19 | export const ConnectModal = (props: Props) => {
20 | const [isOpen, setIsOpen] = useState(false)
21 |
22 | const handleClickOpen = () => setIsOpen(true)
23 |
24 | const handleClose = () => setIsOpen(false)
25 |
26 | return (
27 | <>
28 |
31 |
32 |
33 |
34 | >
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Details/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react';
9 |
10 | import { Section } from '../components/Section';
11 | import { Property } from '../components/Property';
12 |
13 | export const Details = () => {
14 | return (
15 |
16 | postgres:12
17 | 0.4.4
18 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Disks/Disk/Marker/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core'
3 | import clsx from 'clsx'
4 |
5 | import { CircleIcon } from '@postgres.ai/shared/icons/Circle'
6 |
7 | type Props = {
8 | className: string
9 | }
10 |
11 | const useStyles = makeStyles(
12 | {
13 | root: {
14 | display: 'inline',
15 | verticalAlign: 'middle',
16 | width: '10px',
17 | },
18 | },
19 | { index: 1 },
20 | )
21 |
22 | export const Marker = (props: Props) => {
23 | const classes = useStyles()
24 |
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Disks/Disk/ProgressBar/PointerIcon.tsx:
--------------------------------------------------------------------------------
1 | export const PoinerIcon = ({
2 | className,
3 | style,
4 | }: {
5 | className?: string
6 |
7 | style?: React.CSSProperties
8 | }) => (
9 |
20 | )
21 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Retrieval/RefreshFailedAlert/index.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from 'mobx-react-lite'
2 |
3 | import { useStores } from '@postgres.ai/shared/pages/Instance/context'
4 | import { WarningIcon } from '@postgres.ai/shared/icons/Warning'
5 | import { formatDateStd } from '@postgres.ai/shared/utils/date'
6 |
7 | import { Property } from '../../components/Property'
8 |
9 | import styles from './styles.module.scss'
10 |
11 | export const RefreshFailedAlert = observer(() => {
12 | const stores = useStores()
13 |
14 | const refreshFailed =
15 | stores.main.instance?.state?.retrieving?.alerts?.refreshFailed
16 | if (!refreshFailed) return null
17 |
18 | return (
19 |
20 |
21 |
{refreshFailed.message}
22 |
23 |
24 |
25 | {formatDateStd(refreshFailed.lastSeen, { withDistance: true })}
26 |
27 |
28 | {refreshFailed.count}
29 |
30 |
31 | )
32 | })
33 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Retrieval/RefreshFailedAlert/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 |
3 | .root {
4 | padding: 8px 10px;
5 | background: #fff2e5;
6 | border: 1px solid $color-gray-100;
7 | margin-top: 12px;
8 | border-radius: $border-radius--small;
9 | }
10 |
11 | .header {
12 | display: flex;
13 | justify-content: space-between;
14 | align-items: center;
15 | margin-bottom: 8px;
16 | }
17 |
18 | .title {
19 | font-weight: 700;
20 | font-size: 12px;
21 | margin-right: 24px;
22 | margin: 0;
23 | }
24 |
25 | .icon {
26 | width: 10px;
27 | color: $color-status-warning;
28 | flex: 0 0 auto;
29 | }
30 |
31 | .propertyName {
32 | flex: 0 0 123px;
33 | }
34 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Retrieval/RetrievalModal/styles.module.scss:
--------------------------------------------------------------------------------
1 | .tableContainer {
2 | display: flex;
3 | width: 100%;
4 | justify-content: space-between;
5 | flex-direction: row;
6 | }
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Retrieval/RetrievalTable/styles.module.scss:
--------------------------------------------------------------------------------
1 | .tableRow {
2 | display: inline-table !important;
3 | width: 100%;
4 | }
5 |
6 | .tableBody {
7 | height: 100%;
8 |
9 | div:not(:first-child) {
10 | padding-top: 0.75rem;
11 | border-top: 1px solid rgba(224, 224, 224, 1);
12 | }
13 |
14 | div {
15 | padding: 0.75rem 0;
16 | }
17 |
18 | td {
19 | border-bottom: 0;
20 | padding: 0px 18px;
21 | font-size: 13px;
22 | font-family: 'Fira Code', monospace;
23 | }
24 | }
25 |
26 | .tableSubtitle {
27 | font-size: 15px;
28 | font-weight: bold;
29 | }
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Retrieval/utils.ts:
--------------------------------------------------------------------------------
1 | export const getTypeByStatus = (status: string | undefined) => {
2 | if (status === 'finished') return 'ok'
3 | if (status === 'refreshing') return 'waiting'
4 | if (status === 'failed') return 'error'
5 | return 'unknown'
6 | }
7 |
8 | export const isRetrievalUnknown = (mode: string | undefined) => {
9 | return mode === 'unknown' || mode === ''
10 | }
11 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Snapshots/utils.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
9 |
10 | export const getEdgeSnapshots = (snapshots: Snapshot[]) => {
11 | const list = [...snapshots]
12 | const [first] = list
13 | const [last] = list.reverse()
14 | return {
15 | firstSnapshot: (first as Snapshot | undefined) ?? null,
16 | lastSnapshot: (last as Snapshot | undefined) ?? null
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Status/InstanceResponseModal/index.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from 'mobx-react-lite'
2 |
3 | import { Modal } from '@postgres.ai/shared/components/Modal'
4 | import { FormattedText } from '@postgres.ai/shared/components/FormattedText'
5 | import { useStores } from '@postgres.ai/shared/pages/Instance/context'
6 |
7 | import styles from './styles.module.scss'
8 |
9 | type Props = {
10 | isOpen: boolean
11 | onClose: () => void
12 | }
13 |
14 | export const InstanceResponseModal = observer((props: Props) => {
15 | const stores = useStores()
16 |
17 | if (!stores.main.instance) return null
18 |
19 | return (
20 |
26 |
30 |
31 | )
32 | })
33 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Status/InstanceResponseModal/styles.module.scss:
--------------------------------------------------------------------------------
1 | .formattedText {
2 | max-height: 400px;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Status/styles.module.scss:
--------------------------------------------------------------------------------
1 | .controls {
2 | display: flex;
3 | margin-top: 24px;
4 | }
5 |
6 | .button {
7 | text-decoration: none;
8 |
9 | & + .button {
10 | margin-left: 8px;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/Status/utils.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { capitalize } from '@postgres.ai/shared/utils/strings'
9 |
10 | const STATUS_CODE_TO_TYPE = {
11 | OK: 'ok' as const,
12 | WARNING: 'warning' as const,
13 | NO_RESPONSE: 'error' as const
14 | }
15 |
16 | export const getType = (code: keyof typeof STATUS_CODE_TO_TYPE) => {
17 | return STATUS_CODE_TO_TYPE[code] ?? 'unknown'
18 | }
19 |
20 | export const getText = (code: keyof typeof STATUS_CODE_TO_TYPE) => {
21 | if (code === 'OK') return code
22 | if (code === 'NO_RESPONSE') return 'No response'
23 | return capitalize(code)
24 | }
25 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/components/Property/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import cn from 'classnames'
9 |
10 | import styles from './styles.module.scss'
11 |
12 | type Props = {
13 | name: React.ReactNode
14 | children: React.ReactNode
15 | classes?: {
16 | name?: string
17 | content?: string
18 | }
19 | }
20 |
21 | export const Property = (props: Props) => {
22 | const { name, children } = props
23 |
24 | return (
25 |
26 |
27 |
28 | {children}
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/Info/components/Property/styles.module.scss:
--------------------------------------------------------------------------------
1 | .root {
2 | display: flex;
3 | margin-top: 4px;
4 | font-size: 12px;
5 | }
6 |
7 | .name {
8 | flex: 0 0 134px;
9 | margin-right: 14px;
10 |
11 | @media screen and (max-width: '600px') {
12 | flex: 1 1 100%;
13 | margin-right: 7px;
14 | }
15 | }
16 |
17 | .content {
18 | flex: 1 1 100%;
19 | font-weight: 700;
20 | width: 0;
21 | }
22 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/SnapshotsModal/utils.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { formatUTC } from '@postgres.ai/shared/utils/date'
9 |
10 | export const getTags = ({ date, pool }: { date: Date | null, pool: string | null }) => {
11 | const tags = []
12 |
13 | if (date) tags.push({ name: 'Date', value: `${formatUTC(date, 'yyyy-MM-dd')} UTC` })
14 | if (pool) tags.push({ name: 'Disk', value: pool })
15 |
16 | return tags
17 | }
18 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/components/ClonesList/MenuCell/utils.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | export const destroyRestriction = (cloneId: string) => {
9 | const message = `The clone "${cloneId}" is marked as protected. To destroy it, disable the destroy protection first.`
10 | window.alert(message)
11 | }
12 |
13 | export const getResetApprove = (cloneId: string) => {
14 | const message = `Are you sure you want to reset the Database Lab clone: "${cloneId}"?`
15 | return window.confirm(message)
16 | }
17 |
18 | export const getDestroyApprove = (cloneId: string) => {
19 | const message = `Are you sure you want to destroy the Database Lab clone: "${cloneId}"?`
20 | return window.confirm(message)
21 | }
22 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/components/ClonesList/styles.module.scss:
--------------------------------------------------------------------------------
1 | @import '@postgres.ai/shared/styles/vars';
2 |
3 | .interactiveRow {
4 | cursor: pointer;
5 | }
6 |
7 | .verticalCentered {
8 | display: flex;
9 | align-items: center;
10 | }
11 |
12 | .infoIcon {
13 | margin-left: 4px;
14 | height: 12px;
15 | width: 12px;
16 | }
17 |
18 | .sortIcon {
19 | margin-left: 8px;
20 | width: 10px;
21 | }
22 |
23 | .protectionIcon {
24 | display: block;
25 | margin-left: 5px;
26 | height: 1em;
27 | color: $color-gray-semi-dark;
28 | }
29 |
30 | .emptyStub {
31 | margin-top: 16px;
32 | }
33 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/components/ModalReloadButton/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { makeStyles } from '@material-ui/core'
9 |
10 | import { Button } from '@postgres.ai/shared/components/Button2'
11 |
12 | type Props = {
13 | isReloading: boolean
14 | onReload: () => void
15 | }
16 |
17 | const useStyles = makeStyles(
18 | {
19 | spinner: {
20 | margin: '5px',
21 | },
22 | content: {
23 | flex: '0 0 auto',
24 | alignSelf: 'flex-start',
25 | },
26 | },
27 | { index: 1 },
28 | )
29 |
30 | export const ModalReloadButton = (props: Props) => {
31 | const classes = useStyles()
32 |
33 | return (
34 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/components/Tags/index.tsx:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import React from 'react'
9 | import { makeStyles } from '@material-ui/core'
10 |
11 | import { Tag } from './Tag'
12 |
13 | export type TagsProps = {
14 | data: { name: string; value: string }[]
15 | }
16 |
17 | const useStyles = makeStyles(
18 | {
19 | root: {
20 | display: 'flex',
21 | flexWrap: 'wrap',
22 | marginRight: '-8px',
23 | },
24 | },
25 | { index: 1 },
26 | )
27 |
28 | export const Tags = (props: TagsProps) => {
29 | const classes = useStyles()
30 |
31 | return (
32 |
33 | {props.data.map((tag) => {
34 | return (
35 |
36 | {tag.value}
37 |
38 | )
39 | })}
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/context.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { createStrictContext } from '@postgres.ai/shared/utils/react'
9 |
10 | import { Api } from './stores/Main'
11 | import { Stores } from './useCreatedStores'
12 |
13 | export type Host = {
14 | instanceId: string
15 | routes: {
16 | createClone: () => string
17 | clone: (cloneId: string) => string
18 | }
19 | api: Api
20 | title: string
21 | callbacks?: {
22 | showDeprecatedApiBanner: () => void
23 | hideDeprecatedApiBanner: () => void
24 | }
25 | elements: {
26 | breadcrumbs: React.ReactNode
27 | }
28 | wsHost?: string
29 | isPlatform?: boolean
30 | setProjectAlias?: (alias: string) => void
31 | }
32 |
33 | // Host context.
34 | export const { useStrictContext: useHost, Provider: HostProvider } =
35 | createStrictContext()
36 |
37 | // Stores context.
38 | export const { useStrictContext: useStores, Provider: StoresProvider } =
39 | createStrictContext()
40 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/stores/ClonesModal.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { makeAutoObservable } from 'mobx'
9 |
10 | type FilterOptions = {
11 | pool?: string | null
12 | snapshotId?: string
13 | }
14 |
15 | export class ClonesModalStore {
16 | isOpenModal = false
17 | pool: string | null = null
18 | snapshotId: string | null = null
19 |
20 | constructor() {
21 | makeAutoObservable(this)
22 | }
23 |
24 | openModal = (filterOptions: FilterOptions | undefined = {}) => {
25 | const { pool = null, snapshotId = null } = filterOptions
26 |
27 | this.pool = pool
28 | this.snapshotId = snapshotId
29 | this.isOpenModal = true
30 | }
31 |
32 | closeModal = () => {
33 | this.isOpenModal = false
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/stores/SnapshotsModal.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { makeAutoObservable } from 'mobx'
9 |
10 | type FilterOptions = {
11 | pool?: string | null
12 | date?: Date
13 | }
14 |
15 | export class SnapshotsModalStore {
16 | isOpenModal = false
17 | pool: string | null = null
18 | date: Date | null = null
19 |
20 | constructor() {
21 | makeAutoObservable(this)
22 | }
23 |
24 | openModal = (filterOptions: FilterOptions | undefined = {}) => {
25 | const { pool = null, date = null } = filterOptions
26 |
27 | this.pool = pool
28 | this.date = date
29 | this.isOpenModal = true
30 | }
31 |
32 | closeModal = () => {
33 | this.isOpenModal = false
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/styles.scss:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2022, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | #logs-container {
9 | margin: 20px 0 0 0;
10 | border: 1px solid #b4b4b4;
11 | border-radius: 4px;
12 | overflow: hidden;
13 | padding: 0.5rem 1rem;
14 |
15 | & > p {
16 | font-size: small;
17 | font-family: 'Fira Code', monospace;
18 | padding: 1px 0;
19 | }
20 | }
21 |
22 | .error-log {
23 | color: red;
24 | }
25 |
26 | .snackbar-tag {
27 | position: fixed;
28 | bottom: 0;
29 | left: 50%;
30 | transform: translate(-50%, -50%);
31 | background-color: #fff2e5;
32 | color: #000;
33 | font-weight: 500;
34 | font-size: 11px;
35 | padding: 6px 8px;
36 | border-radius: 4px;
37 | transition: all 40050ms ease;
38 | cursor: pointer;
39 | z-index: 1;
40 | }
41 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Instance/useCreatedStores.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 |
3 | import { MainStore } from './stores/Main'
4 | import { ClonesModalStore } from './stores/ClonesModal'
5 | import { SnapshotsModalStore } from './stores/SnapshotsModal'
6 | import { Host } from './context'
7 |
8 | export const useCreatedStores = (host: Host) => ({
9 | main: useMemo(() => new MainStore(host.api), []),
10 | clonesModal: useMemo(() => new ClonesModalStore(), []),
11 | snapshotsModal: useMemo(() => new SnapshotsModalStore(), []),
12 | })
13 |
14 | export type Stores = ReturnType
15 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Logs/Icons/PlusIcon.tsx:
--------------------------------------------------------------------------------
1 | export const PlusIcon = () => (
2 |
8 | )
9 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Logs/constants/index.ts:
--------------------------------------------------------------------------------
1 | export const LOGS_NEW_DATA_MESSAGE =
2 | 'New data arrived below - scroll down to see it 👇🏻'
3 |
4 | export const LAPTOP_WIDTH_PX = 982
5 | export const LOGS_TIME_LIMIT = 20
6 | export const LOGS_LINE_LIMIT = 1000
7 | export const LOGS_ENDPOINT = '/instance/logs'
8 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Logs/utils/index.ts:
--------------------------------------------------------------------------------
1 | export const stringWithoutBrackets = (val: string | undefined) =>
2 | String(val).replace(/[\[\]]/g, '')
3 |
4 | export const stringContainsPattern = (
5 | target: string,
6 | pattern = [
7 | 'base.go',
8 | 'runners.go',
9 | 'snapshots.go',
10 | 'util.go',
11 | 'logging.go',
12 | 'ws.go',
13 | ],
14 | ) => {
15 | let value: number = 0
16 | pattern.forEach(function (word) {
17 | value = value + Number(target?.includes(word))
18 | })
19 | return value === 1
20 | }
21 |
--------------------------------------------------------------------------------
/ui/packages/shared/pages/Logs/wsSnackbar.ts:
--------------------------------------------------------------------------------
1 | import { LOGS_NEW_DATA_MESSAGE } from '@postgres.ai/shared/pages/Logs/constants'
2 |
3 | export const wsSnackbar = (clientAtBottom: boolean, isNewData: boolean) => {
4 | const targetNode = document.getElementById('logs-container')
5 | const snackbarTag = document.createElement('div')
6 |
7 | if (!clientAtBottom && isNewData) {
8 | if (!targetNode?.querySelector('.snackbar-tag')) {
9 | targetNode?.appendChild(snackbarTag)
10 | snackbarTag.classList.add('snackbar-tag')
11 | if (
12 | snackbarTag.childNodes.length === 0 &&
13 | targetNode?.querySelector('p')?.textContent !== 'Not authorized'
14 | ) {
15 | snackbarTag.appendChild(document.createTextNode(LOGS_NEW_DATA_MESSAGE))
16 | }
17 | snackbarTag.onclick = () => {
18 | targetNode?.scroll({
19 | top: targetNode.scrollHeight,
20 | behavior: 'smooth',
21 | })
22 | }
23 | }
24 | } else {
25 | targetNode?.querySelector('.snackbar-tag')?.remove()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ui/packages/shared/scripts/copy-assets.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const glob = require('glob');
4 |
5 | const OUT_DIR = 'dist';
6 |
7 | const PATTERNS = [
8 | '**/*.scss',
9 | '**/*.module.scss',
10 | '**/*.json',
11 | 'react-app-env.d.ts',
12 | ];
13 |
14 | const files = PATTERNS.flatMap(pattern =>
15 | glob.sync(pattern, {
16 | cwd: '.',
17 | ignore: ['node_modules/**', 'dist/**'],
18 | nodir: true,
19 | })
20 | );
21 |
22 | files.forEach((file) => {
23 | const from = path.resolve(file);
24 | const to = path.join(OUT_DIR, file);
25 | const dir = path.dirname(to);
26 | fs.mkdirSync(dir, { recursive: true });
27 | fs.copyFileSync(from, to);
28 | });
29 |
30 | console.log(`✅ Copied ${files.length} assets to dist`);
--------------------------------------------------------------------------------
/ui/packages/shared/styles/global.scss:
--------------------------------------------------------------------------------
1 | @import './vars';
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | font-family: $font-family-text;
11 | font-size: $font-size-main;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | code {
17 | font-family: $font-family-code;
18 | }
19 |
20 | button {
21 | font-family: inherit;
22 | font-size: inherit;
23 | }
24 |
25 | #root {
26 | height: 100vh;
27 | display: flex;
28 | flex-direction: column;
29 | }
30 |
--------------------------------------------------------------------------------
/ui/packages/shared/styles/mixins.scss:
--------------------------------------------------------------------------------
1 | // Screen width.
2 | @mixin screen-max-width ($width) {
3 | @media screen and (max-width: $width) {
4 | @content;
5 | }
6 | }
7 |
8 | @mixin sm {
9 | @include screen-max-width(600px) {
10 | @content;
11 | }
12 | }
13 |
14 | // Transitions.
15 | @mixin transition($properties, $duration, $function) {
16 | $transition: ();
17 | @for $i from 1 through length($properties) {
18 | $transition: append($transition, $duration nth($properties, $i) $function, $separator: comma);
19 | }
20 |
21 | transition: $transition;
22 | }
23 |
24 | @mixin touch-transition ($properties) {
25 | @include transition(($properties), .2s, ease-out);
26 | }
27 |
28 | @mixin animation-transition ($properties) {
29 | @include transition(($properties), .4s, ease-out);
30 | }
31 |
--------------------------------------------------------------------------------
/ui/packages/shared/styles/vars.scss:
--------------------------------------------------------------------------------
1 | // Colors.
2 | $color-white: #fff;
3 |
4 | $color-white--100: #FBFBFB;
5 |
6 | $color-black: #000;
7 |
8 | $color-gray-100: #CCD7DA;
9 | $color-gray: #b4b4b4;
10 |
11 | $color-gray-semi-dark: #808080;
12 |
13 | $color-gray-dark: #444;
14 |
15 | $color-gray-darkest: #333;
16 | $color-gray-darkest--hover: darken($color-gray-darkest, 5);
17 |
18 | $color-orange: #ff6212;
19 | $color-orange--hover: darken($color-orange, 10);
20 |
21 | $color-status-ok: #0db94d;
22 | $color-status-warning: #fd8411;
23 | $color-status-error: #ff2020;
24 | $color-status-waiting: #ffad5f;
25 | $color-status-unknown: #c0c0c0; // prev color-gray
26 |
27 |
28 | $color-blue-main: #0F879D;
29 | $color-blue-dark: #026173;
30 |
31 | // Fonts.
32 | $font-family-text: 'Roboto', sans-serif;
33 | $font-family-code: 'Roboto Mono', monospace;
34 |
35 | // Typography.
36 | $font-size-small: 12px;
37 | $font-size-main: 14px;
38 |
39 | // Shadows.
40 | $box-shadow-layer: 0 2px 6px rgba(0, 0, 0, 0.1);
41 |
42 | // Sizes.
43 | $border-radius--small: 4px;
44 |
--------------------------------------------------------------------------------
/ui/packages/shared/styles/vars.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | // Colors.
9 | const colorGray = '#c0c0c0'
10 |
11 | export const colors = {
12 | white: '#fff',
13 | gray: '#D6D6D6',
14 |
15 | status: {
16 | ok: '#0db94d',
17 | warning: '#fd8411',
18 | error: '#ff2020',
19 | waiting: '#ffad5f',
20 | unknown: colorGray
21 | }
22 | }
23 |
24 | // Mixins.
25 | export const createTransitionInteractive = (...props: string[]) =>
26 | props.map((prop) => `${prop} .2s ease-out`).join(',');
27 |
28 | export const borderRadius = '4px';
29 |
30 | export const resetStyles = {
31 | margin: 0,
32 | padding: 0,
33 | }
34 |
35 | export const resetStylesRoot = {
36 | ...resetStyles,
37 | '& *': {
38 | ...resetStyles
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ui/packages/shared/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "declaration": true,
7 | "emitDeclarationOnly": false,
8 | "noEmit": false,
9 | "module": "esnext",
10 | "target": "es2019",
11 | "moduleResolution": "node",
12 | "jsx": "react-jsx",
13 | "resolveJsonModule": true,
14 | "esModuleInterop": true
15 | },
16 | "include": [
17 | "components/**/*",
18 | "config/**/*",
19 | "helpers/**/*",
20 | "hooks/**/*",
21 | "icons/**/*",
22 | "pages/**/*",
23 | "stores/**/*",
24 | "styles/**/*",
25 | "types/**/*",
26 | "utils/**/*",
27 | "react-app-env.d.ts",
28 | ],
29 | "exclude": [
30 | "node_modules",
31 | "dist",
32 | "meta.json",
33 | "craco.config.js",
34 | "**/*.test.ts",
35 | "**/*.test.tsx"
36 | ]
37 | }
--------------------------------------------------------------------------------
/ui/packages/shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 | "baseUrl": ".",
23 | "paths": {
24 | "@postgres.ai/shared/*": ["./*"]
25 | }
26 | },
27 | "include": [
28 | "."
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/createClone.ts:
--------------------------------------------------------------------------------
1 | import { Clone } from '@postgres.ai/shared/types/api/entities/clone'
2 |
3 | export type CreateClone = (args: {
4 | instanceId: string
5 | cloneId: string
6 | snapshotId: string
7 | dbUser: string
8 | dbPassword: string
9 | isProtected: boolean
10 | }) => Promise<{ response: Clone | null; error: Response | null }>
11 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/destroyClone.ts:
--------------------------------------------------------------------------------
1 | export type DestroyClone = (args: {
2 | instanceId: string
3 | cloneId: string
4 | }) => Promise<{
5 | response: true | null
6 | error: Response | null
7 | }>
8 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getClone.ts:
--------------------------------------------------------------------------------
1 | import { Clone } from '@postgres.ai/shared/types/api/entities/clone'
2 |
3 | export type GetClone = (args: {
4 | instanceId: string
5 | cloneId: string
6 | }) => Promise<{ response: Clone | null; error: Response | null }>
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getConfig.ts:
--------------------------------------------------------------------------------
1 | import { Config } from "../entities/config"
2 |
3 | export type GetConfig = () => Promise<{
4 | response: Config | null
5 | error: Response | null
6 | }>
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getEngine.ts:
--------------------------------------------------------------------------------
1 | export type EngineDto = {
2 | version: string
3 | edition?: string
4 | }
5 |
6 | export type GetEngine = () => Promise<{
7 | response: EngineType | null
8 | error: Response | null
9 | }>
10 |
11 | export const formatEngineDto = (dto: EngineDto) => dto
12 |
13 | export type EngineType = ReturnType
14 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getFullConfig.ts:
--------------------------------------------------------------------------------
1 | export type GetFullConfig = () => Promise<{
2 | response: string | null
3 | error: Response | any | null
4 | }>
5 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getInstance.ts:
--------------------------------------------------------------------------------
1 | import { Instance } from '@postgres.ai/shared/types/api/entities/instance'
2 |
3 | export type GetInstance = (args: { instanceId: string }) => Promise<{
4 | response: Instance | null
5 | error: Response | null
6 | }>
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getInstanceRetrieval.ts:
--------------------------------------------------------------------------------
1 | import { InstanceRetrievalType } from '@postgres.ai/shared/types/api/entities/instanceRetrieval'
2 |
3 | export type GetInstanceRetrieval = (args: { instanceId: string }) => Promise<{
4 | response: InstanceRetrievalType | null
5 | error: Response | null
6 | }>
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getSeImages.ts:
--------------------------------------------------------------------------------
1 | export type GetSeImages = (args: {
2 | packageGroup: string
3 | platformUrl?: string
4 | }) => Promise<{
5 | response: InstanceRetrievalType | null
6 | error: Response | null
7 | }>
8 |
9 | export interface SeImages {
10 | org_id?: number
11 | package_group: string
12 | pg_major_version: string
13 | tag: string
14 | pg_config_presets?: {
15 | shared_preload_libraries: string
16 | }
17 | location: string
18 | }
19 |
20 | export const formatSeImages = (seImages: SeImages[]) => seImages
21 |
22 | export type InstanceRetrievalType = ReturnType
23 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getSnapshots.ts:
--------------------------------------------------------------------------------
1 | import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
2 |
3 | export type GetSnapshots = (args: { instanceId: string }) => Promise<{
4 | response: Snapshot[] | null
5 | error: Response | null
6 | }>
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/getWSToken.ts:
--------------------------------------------------------------------------------
1 | import { WSToken } from '@postgres.ai/shared/types/api/entities/wsToken'
2 |
3 | export type GetWSToken = (args: { instanceId: string }) => Promise<{
4 | response: WSToken | null
5 | error: Response | null
6 | }>
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/initWS.ts:
--------------------------------------------------------------------------------
1 | export type InitWS = ( path: string, token: string ) => WebSocket
2 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/refreshInstance.ts:
--------------------------------------------------------------------------------
1 | export type RefreshInstance = (args: { instanceId: string }) => Promise<{
2 | response: boolean | null
3 | error: Response | null
4 | }>
5 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/resetClone.ts:
--------------------------------------------------------------------------------
1 | export type ResetClone = (args: {
2 | instanceId: string
3 | cloneId: string
4 | snapshotId: string
5 | }) => Promise<{
6 | response: true | null
7 | error: Response | null
8 | }>
9 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/updateClone.ts:
--------------------------------------------------------------------------------
1 | export type UpdateClone = (args: {
2 | instanceId: string
3 | cloneId: string
4 | clone: {
5 | isProtected: boolean
6 | }
7 | }) => Promise<{
8 | response: true | null
9 | error: Response | null
10 | }>
11 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/endpoints/updateConfig.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@postgres.ai/shared/types/api/entities/config'
2 |
3 | export type UpdateConfig = (values: Config) => Promise<{
4 | response: Response | null
5 | error: Response | null
6 | }>
7 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/entities/dbSource.ts:
--------------------------------------------------------------------------------
1 | export type dbSource = {
2 | host: string
3 | port: string
4 | dbname: string
5 | username: string
6 | password: string
7 | db_list?: string[]
8 | }
9 |
10 | export type TestSourceDTO = {
11 | message: string
12 | status: string
13 | }
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/entities/pool.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | export type PoolDto = {
9 | cloneList: string[]
10 | fileSystem: {
11 | compressRatio: number
12 | dataSize: number
13 | free: number
14 | mode: string
15 | size: number
16 | used: number
17 | usedByClones: number
18 | usedBySnapshots: number
19 | }
20 | mode: string
21 | name: string
22 | status: 'active' | 'empty' | 'refreshing'
23 | }
24 |
25 | export const formatPoolDto = (dto: PoolDto) => dto
26 |
27 | export type Pool = ReturnType
28 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/entities/snapshot.ts:
--------------------------------------------------------------------------------
1 | import { parseDate } from '@postgres.ai/shared/utils/date'
2 |
3 | export type SnapshotDto = {
4 | createdAt: string
5 | dataStateAt: string
6 | id: string
7 | pool: string
8 | physicalSize: number
9 | logicalSize: number
10 | }
11 |
12 | export const formatSnapshotDto = (dto: SnapshotDto) => ({
13 | ...dto,
14 | createdAtDate: parseDate(dto.createdAt),
15 | dataStateAtDate: parseDate(dto.dataStateAt)
16 | })
17 |
18 | export type Snapshot = ReturnType
19 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/api/entities/wsToken.ts:
--------------------------------------------------------------------------------
1 | export type WSTokenDTO = {
2 | token: string
3 | }
4 |
5 | export const formatWSTokenDto = (dto: WSTokenDTO) => dto
6 |
7 | export type WSToken = ReturnType
8 |
--------------------------------------------------------------------------------
/ui/packages/shared/types/byte-size/index.d.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | type Options = {
9 | precision: number
10 | units: 'metric' | 'iec' | 'metric_octet' | 'iec_octet'
11 | }
12 |
13 | type Result = {
14 | value: string
15 | unit: string
16 | long: string
17 | }
18 |
19 | declare module 'byte-size' {
20 | declare const byteSize: (bytes: number, options?: Options) => Result
21 | export default byteSize
22 | }
23 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/api.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | export const getTextFromUnknownApiError = async (error: Response) => {
9 | const log = (text: string) => console.error('Unknown API error', text)
10 |
11 | try {
12 | const result = await error.json()
13 | log(result)
14 | return JSON.stringify(result)
15 | } catch (e) {
16 | // not a json
17 | }
18 |
19 | try {
20 | const result = await error.text()
21 | log(result)
22 | return result
23 | } catch (e) {
24 | // not a text
25 | }
26 |
27 | const result = `${error.status} ${error.statusText}`
28 | log(result)
29 | return result
30 | }
31 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/clone.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import { Clone } from '@postgres.ai/shared/types/api/entities/clone'
9 | import { capitalize } from '@postgres.ai/shared/utils/strings'
10 |
11 | const STATUS_CODE_TO_TYPE = {
12 | OK: 'ok' as const,
13 | CREATING: 'waiting' as const,
14 | DELETING: 'waiting' as const,
15 | RESETTING: 'waiting' as const,
16 | FATAL: 'error' as const,
17 | }
18 |
19 | type StatusCode = keyof typeof STATUS_CODE_TO_TYPE
20 |
21 | export const getCloneStatusType = (statusCode: StatusCode) =>
22 | STATUS_CODE_TO_TYPE[statusCode] ?? 'unknown'
23 |
24 | export const getCloneStatusText = (statusCode: StatusCode) => {
25 | if (statusCode === 'OK') return statusCode
26 |
27 | return capitalize(statusCode)
28 | }
29 |
30 | export const checkIsCloneStable = (clone: Clone) =>
31 | clone.status.code === 'OK' || clone.status.code === 'FATAL'
32 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/instance.ts:
--------------------------------------------------------------------------------
1 | export const parseEngineVersion = (str: string) => {
2 | const [versionStr] = str.split('-')
3 | const [major, minor, patch] = versionStr.split('.')
4 | if (!major || !minor || !patch) return null
5 | return {
6 | major: Number(major),
7 | minor: Number(minor),
8 | patch: Number(patch),
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/numbers.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | export const round = (value: number, precision = 0) => {
9 | const multiplier = 10 ** precision
10 | return Math.round(value * multiplier) / multiplier
11 | }
12 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/react.ts:
--------------------------------------------------------------------------------
1 | import { useContext, createContext } from 'react'
2 |
3 | export const createStrictContext = () => {
4 | const Context = createContext(undefined as unknown as Value)
5 |
6 | return {
7 | useStrictContext: () => useContext(Context),
8 | Provider: Context.Provider,
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/snapshot.ts:
--------------------------------------------------------------------------------
1 | import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
2 |
3 | export const compareSnapshotsDesc = (a: Snapshot, b: Snapshot) =>
4 | b.dataStateAtDate.getTime() - a.dataStateAtDate.getTime()
5 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/strings.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | export const capitalize = (value: string) => {
9 | const [firstChar, ...otherChars] = value.split('')
10 | return `${firstChar.toUpperCase()}${otherChars.join('').toLowerCase()}`
11 | }
12 |
--------------------------------------------------------------------------------
/ui/packages/shared/utils/units.ts:
--------------------------------------------------------------------------------
1 | /*--------------------------------------------------------------------------
2 | * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3 | * All Rights Reserved. Proprietary and confidential.
4 | * Unauthorized copying of this file, via any medium is strictly prohibited
5 | *--------------------------------------------------------------------------
6 | */
7 |
8 | import byteSize from 'byte-size'
9 |
10 | type Options = {
11 | precision?: number
12 | }
13 |
14 | export const formatBytesIEC = (bytes: number, options?: Options) => {
15 | const { precision = 3 } = options ?? {}
16 |
17 | const result = byteSize(bytes, {
18 | precision,
19 | units: 'iec',
20 | })
21 |
22 | return `${result.value} ${result.unit}`
23 | }
24 |
--------------------------------------------------------------------------------
/ui/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # include packages in subfolders (e.g. apps/ and packages/)
3 | - 'packages/*'
4 | # if required, exclude some directories
5 | - '!**/test/**'
6 |
--------------------------------------------------------------------------------