├── .dockerignore ├── .github ├── pull_request_template.md └── workflows │ ├── cleanup.yaml │ ├── containers.yaml │ ├── docs.yaml │ └── test.yaml ├── .gitignore ├── .gitmodules ├── .husky ├── commit-msg └── pre-commit ├── LICENSE ├── README.md ├── cmd ├── ensign │ └── main.go ├── halyard │ └── main.go ├── quarterdeck │ └── main.go ├── sendgrid │ └── main.go ├── tenant │ └── main.go └── uptime │ └── main.go ├── containers ├── beacon │ ├── Dockerfile │ └── nginx.conf ├── build.sh ├── cleangcr.py ├── docker-compose.yaml ├── ensign │ ├── .gitignore │ └── Dockerfile ├── local.sh ├── monitor │ ├── .gitignore │ ├── grafana │ │ ├── .gitkeep │ │ └── dashboards │ │ │ ├── .gitkeep │ │ │ └── ensign.json │ └── prometheus.yml ├── placeholder │ └── Dockerfile ├── quarterdeck │ ├── .gitignore │ ├── Dockerfile │ └── keys │ │ ├── 01GE6191AQTGMCJ9BN0QC3CCVG.pem │ │ └── 01GE62EXXR0X0561XD53RDFBQJ.pem ├── tenant │ ├── .gitignore │ ├── Dockerfile │ └── db │ │ └── .gitkeep └── uptime │ ├── .gitignore │ ├── Dockerfile │ ├── db │ └── .gitkeep │ └── services.json ├── docs ├── archetypes │ └── default.md ├── assets │ ├── images │ │ ├── banner.jpg │ │ ├── cta-illustration.jpg │ │ ├── favicon.png │ │ └── logo.png │ └── scss │ │ └── custom.scss ├── config.toml ├── config │ └── _default │ │ ├── config.toml │ │ ├── menus.en.toml │ │ ├── menus.fr.toml │ │ ├── module.toml │ │ └── params.toml ├── content │ ├── _index.en.md │ ├── ensql │ │ ├── _index.en.md │ │ ├── operators.en.md │ │ └── syntax │ │ │ ├── _index.en.md │ │ │ └── images │ │ │ └── SELECT.png │ ├── eventing │ │ ├── _index.md │ │ ├── data_sources.md │ │ ├── glossary.md │ │ └── use_cases.md │ ├── examples │ │ ├── _index.md │ │ ├── data_engineers.md │ │ └── data_scientists.md │ ├── faq │ │ └── _index.md │ ├── getting-started │ │ ├── _index.md │ │ ├── edas.md │ │ └── topics.md │ ├── sdk │ │ ├── _index.md │ │ ├── golang.md │ │ └── python.md │ ├── system │ │ ├── _index.md │ │ ├── broker.md │ │ ├── configuration.md │ │ ├── groups.md │ │ └── staging.md │ └── topics │ │ └── _index.md ├── go.mod ├── go.sum ├── i18n │ ├── en.yaml │ └── fr.yaml ├── netlify.toml ├── package-lock.json ├── package.json ├── postcss.config.js ├── static │ └── img │ │ ├── baleen_diagram.png │ │ ├── broker.png │ │ ├── comic.png │ │ ├── database_record.png │ │ ├── dbs_v_events.png │ │ ├── detective.png │ │ ├── ensign-squad.png │ │ ├── enson-laptop.png │ │ ├── favicon.png │ │ ├── logo.png │ │ ├── mascot.jpg │ │ ├── sample-eda.png │ │ ├── smol-laptop.png │ │ └── weather.png └── themes │ └── godocs-2 │ ├── .editorconfig │ ├── .gitignore │ ├── .jshintrc │ ├── .prettierrc │ ├── archetypes │ └── default.md │ ├── assets │ ├── js │ │ ├── bootstrap.js │ │ └── script.js │ ├── plugins │ │ ├── masonry │ │ │ └── masonry.min.js │ │ └── webfont │ │ │ └── webfont-loader-2.js │ └── scss │ │ ├── _buttons.scss │ │ ├── _common.scss │ │ ├── _mixins.scss │ │ ├── _typography.scss │ │ ├── style.scss │ │ └── templates │ │ ├── _bootstrap.scss │ │ ├── _main.scss │ │ ├── _navigation.scss │ │ └── search-modal.scss │ ├── config.toml │ ├── layouts │ ├── 404.html │ ├── _default │ │ ├── baseof.html │ │ ├── changelog.html │ │ ├── contact.html │ │ ├── index.json │ │ ├── list.html │ │ ├── search.html │ │ └── single.html │ ├── index.html │ ├── partials │ │ ├── components │ │ │ ├── page-header.html │ │ │ └── search-modal.html │ │ ├── default.html │ │ └── essentials │ │ │ ├── footer.html │ │ │ ├── head.html │ │ │ ├── header.html │ │ │ ├── script.html │ │ │ └── style.html │ └── shortcodes │ │ ├── changelog.html │ │ └── faq.html │ ├── netlify.toml │ ├── package.json │ └── postcss.config.js ├── go.mod ├── go.sum ├── pkg ├── ensign │ ├── api │ │ ├── generate.go │ │ ├── generate.sh │ │ └── v1beta1 │ │ │ ├── codes.go │ │ │ ├── deduplication.go │ │ │ ├── deduplication_test.go │ │ │ ├── ensign.pb.go │ │ │ ├── ensign_grpc.pb.go │ │ │ ├── errors.go │ │ │ ├── event.go │ │ │ ├── event.pb.go │ │ │ ├── event_test.go │ │ │ ├── groups.go │ │ │ ├── groups.pb.go │ │ │ ├── groups_test.go │ │ │ ├── query.pb.go │ │ │ ├── testdata │ │ │ └── events.json │ │ │ ├── topic.go │ │ │ ├── topic.pb.go │ │ │ └── topic_test.go │ ├── broker │ │ ├── broker.go │ │ ├── broker_suite_test.go │ │ ├── broker_test.go │ │ ├── errors.go │ │ ├── msgs.go │ │ └── msgs_test.go │ ├── buffer │ │ ├── buffer.go │ │ ├── buffer_test.go │ │ ├── channel.go │ │ ├── channel_test.go │ │ ├── errors.go │ │ ├── ring.go │ │ └── ring_test.go │ ├── config │ │ ├── config.go │ │ ├── config_test.go │ │ └── testdata │ │ │ ├── config.yaml │ │ │ └── partial.yaml │ ├── contexts │ │ ├── contexts.go │ │ ├── contexts_test.go │ │ ├── errors.go │ │ ├── stream.go │ │ └── stream_test.go │ ├── duplicates.go │ ├── duplicates_test.go │ ├── ensql │ │ ├── ensql.go │ │ ├── ensql_test.go │ │ ├── errors.go │ │ ├── predicate.go │ │ ├── predicate_test.go │ │ ├── query.go │ │ ├── query_test.go │ │ ├── step.go │ │ ├── tokens.go │ │ └── tokens_test.go │ ├── events.go │ ├── events_test.go │ ├── info.go │ ├── info │ │ ├── info.go │ │ ├── info_test.go │ │ └── testdata │ │ │ ├── phase1.json │ │ │ └── phase2.json │ ├── info_test.go │ ├── interceptors │ │ ├── auth.go │ │ ├── auth_test.go │ │ ├── interceptors_test.go │ │ ├── maintenance.go │ │ ├── maintenance_test.go │ │ ├── monitoring.go │ │ ├── monitoring_test.go │ │ ├── recovery.go │ │ └── recovery_test.go │ ├── mimetype │ │ ├── generate.go │ │ ├── generate.sh │ │ └── v1beta1 │ │ │ ├── mimetype.go │ │ │ ├── mimetype.pb.go │ │ │ └── mimetype_test.go │ ├── mock │ │ ├── creds.go │ │ ├── mock.go │ │ └── stream.go │ ├── o11y │ │ ├── o11y.go │ │ └── o11y_test.go │ ├── query.go │ ├── region │ │ ├── generate.go │ │ ├── generate.sh │ │ └── v1beta1 │ │ │ └── region.pb.go │ ├── rlid │ │ ├── errors.go │ │ ├── rlid.go │ │ ├── rlid_test.go │ │ ├── sequence.go │ │ └── sequence_test.go │ ├── server.go │ ├── server_test.go │ ├── status.go │ ├── status_test.go │ ├── store │ │ ├── errors │ │ │ ├── errors.go │ │ │ ├── errors_test.go │ │ │ └── iter.go │ │ ├── events │ │ │ ├── events.go │ │ │ ├── events_suite_test.go │ │ │ ├── events_test.go │ │ │ ├── indash.go │ │ │ ├── indash_test.go │ │ │ ├── iterator.go │ │ │ ├── keys.go │ │ │ ├── keys_test.go │ │ │ └── testdata │ │ │ │ ├── events.pb.json │ │ │ │ └── readonly.zip │ │ ├── iterator │ │ │ └── iface.go │ │ ├── meta │ │ │ ├── groups.go │ │ │ ├── groups_test.go │ │ │ ├── info.go │ │ │ ├── info_test.go │ │ │ ├── keys.go │ │ │ ├── keys_test.go │ │ │ ├── meta.go │ │ │ ├── meta_test.go │ │ │ ├── names.go │ │ │ ├── names_test.go │ │ │ ├── segment.go │ │ │ ├── segment_test.go │ │ │ ├── testdata │ │ │ │ ├── groups.json │ │ │ │ ├── readonly.zip │ │ │ │ ├── topic_infos.json │ │ │ │ └── topics.json │ │ │ ├── topics.go │ │ │ └── topics_test.go │ │ ├── mock │ │ │ ├── iterator.go │ │ │ ├── iterator_test.go │ │ │ ├── json.go │ │ │ ├── json_test.go │ │ │ ├── mock.go │ │ │ ├── mock_test.go │ │ │ └── testdata │ │ │ │ ├── events.pb.json │ │ │ │ ├── topic_infos.pb.json │ │ │ │ ├── topic_names.pb.json │ │ │ │ └── topics.pb.json │ │ └── store.go │ ├── testdata │ │ ├── albums.json │ │ ├── listens.json │ │ ├── queries.txt │ │ ├── topic.json │ │ ├── topic_infos.json │ │ └── topics.json │ ├── topics.go │ ├── topics │ │ ├── topics.go │ │ └── topics_test.go │ └── topics_test.go ├── quarterdeck │ ├── accounts.go │ ├── accounts_test.go │ ├── api │ │ └── v1 │ │ │ ├── api.go │ │ │ ├── api_test.go │ │ │ ├── client.go │ │ │ ├── client_test.go │ │ │ ├── context.go │ │ │ ├── context_test.go │ │ │ ├── creds.go │ │ │ ├── errors.go │ │ │ ├── errors_test.go │ │ │ ├── options.go │ │ │ └── refresh.go │ ├── apikeys.go │ ├── apikeys_test.go │ ├── auth.go │ ├── auth_test.go │ ├── authtest │ │ ├── authtest.go │ │ └── authtest_test.go │ ├── config │ │ ├── config.go │ │ └── config_test.go │ ├── db │ │ ├── db.go │ │ ├── db_test.go │ │ ├── errors.go │ │ ├── migrations │ │ │ ├── 0000_migrations.sql │ │ │ ├── 0001_initial_schema.sql │ │ │ ├── 0002_default_data.sql │ │ │ ├── 0003_email_verification.sql │ │ │ ├── 0004_user_invitations.sql │ │ │ ├── 0005_user_org_logins.sql │ │ │ ├── 0006_revoked_timestamp.sql │ │ │ └── 0007_account_type.sql │ │ ├── models │ │ │ ├── apikeys.go │ │ │ ├── apikeys_test.go │ │ │ ├── count.go │ │ │ ├── count_test.go │ │ │ ├── errors.go │ │ │ ├── models.go │ │ │ ├── models_test.go │ │ │ ├── orgs.go │ │ │ ├── orgs_test.go │ │ │ ├── projects.go │ │ │ ├── projects_test.go │ │ │ ├── roles.go │ │ │ ├── subject.go │ │ │ ├── subject_test.go │ │ │ ├── testdata │ │ │ │ └── fixtures.sql │ │ │ ├── users.go │ │ │ └── users_test.go │ │ ├── schema.go │ │ ├── schema_test.go │ │ ├── tokens.go │ │ └── tokens_test.go │ ├── emails.go │ ├── emails_test.go │ ├── invites.go │ ├── invites_test.go │ ├── keygen │ │ ├── keygen.go │ │ └── keygen_test.go │ ├── middleware │ │ ├── auth.go │ │ ├── auth_test.go │ │ ├── csrf.go │ │ ├── csrf_test.go │ │ ├── errors.go │ │ ├── ratelimiter.go │ │ └── ratelimiter_test.go │ ├── mock │ │ ├── quarterdeck.go │ │ └── quarterdeck_test.go │ ├── orgs.go │ ├── orgs_test.go │ ├── passwd │ │ ├── dk.go │ │ ├── dk_test.go │ │ ├── strength.go │ │ └── strength_test.go │ ├── permissions │ │ ├── permissions.go │ │ └── permissions_test.go │ ├── projects.go │ ├── projects_test.go │ ├── quarterdeck.go │ ├── quarterdeck_test.go │ ├── replica │ │ └── query │ │ │ ├── generate.go │ │ │ └── v1beta1 │ │ │ └── query.pb.go │ ├── report │ │ ├── errors.go │ │ ├── report.go │ │ └── report_test.go │ ├── status.go │ ├── status_test.go │ ├── testdata │ │ ├── 01GE6191AQTGMCJ9BN0QC3CCVG.pem │ │ ├── 01GE62EXXR0X0561XD53RDFBQJ.pem │ │ ├── fixtures.sql │ │ └── security.txt │ ├── tokens │ │ ├── cache.go │ │ ├── cache_test.go │ │ ├── claims.go │ │ ├── claims_test.go │ │ ├── errors.go │ │ ├── expires.go │ │ ├── expires_test.go │ │ ├── jwks.go │ │ ├── jwks_test.go │ │ ├── mock.go │ │ ├── testdata │ │ │ ├── 01GE6191AQTGMCJ9BN0QC3CCVG.pem │ │ │ ├── 01GE62EXXR0X0561XD53RDFBQJ.pem │ │ │ ├── jwks.json │ │ │ └── partial_jwks.json │ │ ├── tokens.go │ │ ├── tokens_test.go │ │ └── validator.go │ ├── users.go │ ├── users_test.go │ ├── wellknown.go │ └── wellknown_test.go ├── raft │ ├── api │ │ ├── generate.go │ │ └── v1beta1 │ │ │ ├── log.go │ │ │ ├── log.pb.go │ │ │ ├── raft.pb.go │ │ │ └── raft_grpc.pb.go │ ├── config.go │ ├── config_test.go │ ├── election │ │ ├── election.go │ │ └── election_test.go │ ├── errors.go │ ├── interval │ │ ├── interval.go │ │ └── interval_test.go │ ├── log │ │ ├── errors.go │ │ ├── iface.go │ │ ├── log.go │ │ ├── log_test.go │ │ ├── mock │ │ │ ├── statemachine.go │ │ │ └── sync.go │ │ ├── options.go │ │ └── testdata │ │ │ ├── entries.pb.json │ │ │ └── meta.pb.json │ ├── peers │ │ ├── peers.go │ │ ├── peers_test.go │ │ └── testdata │ │ │ ├── quorum.foo │ │ │ ├── quorum.json │ │ │ └── quorum.yaml │ ├── raft.go │ ├── raft_test.go │ ├── state.go │ └── testdata │ │ └── quorum.json ├── tenant │ ├── api │ │ └── v1 │ │ │ ├── api.go │ │ │ ├── client.go │ │ │ ├── client_test.go │ │ │ ├── errors.go │ │ │ ├── errors_test.go │ │ │ └── options.go │ ├── apikeys.go │ ├── apikeys_test.go │ ├── auth.go │ ├── auth_test.go │ ├── client.go │ ├── config │ │ ├── config.go │ │ └── config_test.go │ ├── db │ │ ├── db.go │ │ ├── db_test.go │ │ ├── errors.go │ │ ├── keys.go │ │ ├── keys_test.go │ │ ├── members.go │ │ ├── members_test.go │ │ ├── organizations.go │ │ ├── organizations_test.go │ │ ├── projects.go │ │ ├── projects_test.go │ │ ├── tenants.go │ │ ├── tenants_test.go │ │ ├── testdata │ │ │ └── tenant.json │ │ ├── time.go │ │ ├── topics.go │ │ ├── topics_test.go │ │ ├── users.go │ │ └── users_test.go │ ├── ensign.go │ ├── ensign_test.go │ ├── fixtures.go │ ├── invites.go │ ├── invites_test.go │ ├── members.go │ ├── members_test.go │ ├── organization_test.go │ ├── organizations.go │ ├── profiles.go │ ├── profiles_test.go │ ├── projects.go │ ├── projects_test.go │ ├── status.go │ ├── status_test.go │ ├── tenant.go │ ├── tenant_test.go │ ├── tenants.go │ ├── tenants_test.go │ ├── topics.go │ └── topics_test.go ├── uptime │ ├── config │ │ └── config.go │ ├── db │ │ ├── db.go │ │ ├── iface.go │ │ ├── status.go │ │ └── tx.go │ ├── health │ │ ├── api.go │ │ ├── api_test.go │ │ ├── ensign.go │ │ ├── errors.go │ │ ├── health.go │ │ ├── health_test.go │ │ ├── http.go │ │ ├── http_test.go │ │ ├── options.go │ │ ├── status.go │ │ ├── status_test.go │ │ └── version.go │ ├── incident │ │ ├── db.go │ │ ├── incident.go │ │ └── incident_test.go │ ├── monitor.go │ ├── services │ │ └── services.go │ ├── static │ │ ├── favicon.png │ │ ├── logo-white.png │ │ └── logo.png │ ├── templates │ │ ├── incidents.html │ │ ├── index.html │ │ └── services.html │ ├── uptime.go │ ├── web.go │ └── web_test.go ├── utils │ ├── backups │ │ ├── backups.go │ │ ├── backups_test.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── errors.go │ │ ├── leveldb.go │ │ ├── leveldb_test.go │ │ ├── sqlite3.go │ │ ├── sqlite3_test.go │ │ ├── storage.go │ │ └── testdata │ │ │ ├── .gitkeep │ │ │ ├── leveldb.tgz │ │ │ └── sqlite.db │ ├── bufconn │ │ └── listener.go │ ├── emails │ │ ├── .gitignore │ │ ├── builders.go │ │ ├── builders_test.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── contacts.go │ │ ├── emails_test.go │ │ ├── errors.go │ │ ├── iface.go │ │ ├── mock │ │ │ └── mock.go │ │ ├── subjects.go │ │ ├── templates │ │ │ ├── daily_users.html │ │ │ ├── daily_users.txt │ │ │ ├── invite.html │ │ │ ├── invite.txt │ │ │ ├── partials │ │ │ │ ├── base.html │ │ │ │ ├── footer.html │ │ │ │ └── style.html │ │ │ ├── password_reset_request.html │ │ │ ├── password_reset_request.txt │ │ │ ├── password_reset_success.html │ │ │ ├── password_reset_success.txt │ │ │ ├── verify_email.html │ │ │ ├── verify_email.txt │ │ │ ├── welcome.html │ │ │ └── welcome.txt │ │ └── testdata │ │ │ └── foo.zip │ ├── gravatar │ │ ├── gravatar.go │ │ └── gravatar_test.go │ ├── keymu │ │ ├── keymu.go │ │ └── keymu_test.go │ ├── logger │ │ ├── grpc.go │ │ ├── level.go │ │ ├── level_test.go │ │ ├── logger.go │ │ ├── logger_test.go │ │ ├── middleware.go │ │ └── testing.go │ ├── metatopic │ │ ├── errors.go │ │ ├── metatopic.go │ │ └── metatopic_test.go │ ├── metrics │ │ └── metrics.go │ ├── mtls │ │ ├── errors.go │ │ ├── mtls.go │ │ ├── mtls_test.go │ │ ├── pem │ │ │ ├── errors.go │ │ │ ├── io.go │ │ │ ├── io_test.go │ │ │ ├── pem.go │ │ │ └── pem_test.go │ │ ├── pool.go │ │ ├── provider.go │ │ ├── provider_test.go │ │ └── testdata │ │ │ ├── README.md │ │ │ ├── astros.com.pool.pem │ │ │ ├── banks.com.pool.pem │ │ │ ├── client.astros.com.pem │ │ │ ├── client.banks.com.pem │ │ │ ├── server.astros.com.pem │ │ │ └── server.banks.com.pem │ ├── pagination │ │ ├── generate.go │ │ ├── pagination.go │ │ ├── pagination.pb.go │ │ └── pagination_test.go │ ├── probez │ │ ├── client.go │ │ ├── generate.go │ │ ├── grpc │ │ │ └── v1 │ │ │ │ ├── health.pb.go │ │ │ │ ├── health_grpc.pb.go │ │ │ │ ├── probez.go │ │ │ │ └── probez_test.go │ │ ├── probez.go │ │ ├── probez_test.go │ │ ├── server.go │ │ └── server_test.go │ ├── radish │ │ ├── config.go │ │ ├── config_test.go │ │ ├── error.go │ │ ├── error_test.go │ │ ├── options.go │ │ ├── options_test.go │ │ ├── radish.go │ │ ├── radish_test.go │ │ ├── scheduler.go │ │ ├── scheduler_test.go │ │ ├── tasks.go │ │ └── tasks_test.go │ ├── random │ │ ├── names.go │ │ └── names_test.go │ ├── responses │ │ ├── errors.go │ │ └── marshal.go │ ├── rows │ │ └── rows.go │ ├── sendgrid │ │ ├── api.go │ │ ├── contacts.go │ │ ├── contacts_test.go │ │ └── mail.go │ ├── sentry │ │ ├── config.go │ │ ├── config_test.go │ │ ├── context.go │ │ ├── context_test.go │ │ ├── error.go │ │ ├── interceptors.go │ │ ├── logger.go │ │ ├── sample.go │ │ ├── sample_test.go │ │ └── sentry.go │ ├── service │ │ ├── error.go │ │ ├── options.go │ │ ├── service.go │ │ └── service_test.go │ ├── sqlite │ │ ├── sqlite.go │ │ └── sqlite_test.go │ ├── tlstest │ │ ├── testcert.go │ │ └── tlstest.go │ ├── tokens │ │ ├── tokens.go │ │ └── tokens_test.go │ ├── ulids │ │ ├── ulids.go │ │ └── ulids_test.go │ └── units │ │ ├── units.go │ │ └── units_test.go └── version.go ├── postman ├── collections │ ├── quarterdeck.json │ └── tenant.json └── schemas │ ├── quarterdeck.yaml │ └── tenant.yaml ├── proto ├── .gitkeep ├── api │ └── v1beta1 │ │ ├── ensign.proto │ │ ├── event.proto │ │ ├── groups.proto │ │ ├── query.proto │ │ └── topic.proto ├── grpc │ └── health │ │ └── v1 │ │ └── health.proto ├── mimetype │ └── v1beta1 │ │ └── mimetype.proto ├── pagination │ └── pagination.proto ├── quarterdeck │ └── query │ │ └── v1beta1 │ │ └── query.proto ├── raft │ └── v1beta1 │ │ ├── log.proto │ │ └── raft.proto └── region │ └── v1beta1 │ └── region.proto └── web ├── beacon-app ├── .babelrc ├── .env.template ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .linguirc ├── .nvmrc ├── .nyc_output │ └── processinfo │ │ └── index.json ├── .prettierrc ├── .storybook │ ├── main.js │ ├── preview-head.html │ └── preview.js ├── README.md ├── cypress.config.ts ├── cypress │ ├── e2e │ │ ├── ExistingMember.feature │ │ ├── ExistingMember │ │ │ ├── ExistingMemberHasAcount.ts │ │ │ └── ExistingMemberHasNotAcount.ts │ │ ├── ForgotPassword.feature │ │ ├── ForgotPassword │ │ │ └── ForgotPassword.ts │ │ ├── GenerateAPIKey.feature │ │ ├── GenerateAPIKey │ │ │ └── GenerateAPIKey.ts │ │ ├── HomePage.feature │ │ ├── HomePage │ │ │ └── HomePage.ts │ │ ├── InviteMember.feature │ │ ├── InviteMember │ │ │ └── InviteMember.ts │ │ ├── Login.feature │ │ ├── Login │ │ │ └── Login.ts │ │ ├── Onboarding.feature │ │ ├── Onboarding │ │ │ └── Onboarding.ts │ │ ├── OnboardingInvitationUser.feature │ │ ├── OnboardingInvitationUser │ │ │ └── OnboardingInvitationUser.ts │ │ ├── OrganizationDashboard.feature │ │ ├── OrganizationDashboard │ │ │ └── OrganizationDashboard.ts │ │ ├── ProjectDetail.feature │ │ ├── ProjectDetail │ │ │ └── ProjectDetail.ts │ │ ├── ProjectDetailPage.feature │ │ ├── ProjectDetailPage │ │ │ └── ProjectDetailPage.ts │ │ ├── ProjectsPage.feature │ │ ├── ProjectsPage │ │ │ └── ProjectsPage.ts │ │ ├── Registration.feature │ │ ├── Registration │ │ │ └── Registation.ts │ │ ├── ResetPassword.feature │ │ ├── ResetPassword │ │ │ └── ResetPassword.ts │ │ ├── Team.feature │ │ ├── Team │ │ │ └── Team.ts │ │ ├── TopicDetail.feature │ │ ├── TopicDetail │ │ │ └── TopicDetail.ts │ │ ├── TopicQuery.feature │ │ ├── TopicQuery │ │ │ └── TopicQuery.ts │ │ ├── UserProfile.feature │ │ └── UserProfile │ │ │ └── UserProfile.ts │ ├── fixtures │ │ └── user.json │ ├── plugins │ │ └── index.ts │ ├── support │ │ ├── commands.ts │ │ └── e2e.ts │ └── tsconfig.json ├── index.html ├── lingui.config.js ├── package.json ├── postcss.config.js ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── favicon.png │ ├── logo.png │ ├── manifest.json │ ├── mstile-150x150.png │ ├── robots.txt │ └── safari-pinned-tab.svg ├── setupTests.js ├── src │ ├── App.tsx │ ├── I18n.ts │ ├── application │ │ ├── api │ │ │ ├── ApiAdapters.ts │ │ │ └── ApiService.ts │ │ ├── config │ │ │ ├── appConfig.tsx │ │ │ ├── appEnv.ts │ │ │ ├── index.tsx │ │ │ ├── react-query.tsx │ │ │ └── sentry.ts │ │ ├── index.ts │ │ ├── routes │ │ │ ├── PublicRoutes.tsx │ │ │ ├── index.ts │ │ │ ├── paths.ts │ │ │ ├── privateRoute.tsx │ │ │ └── root.tsx │ │ └── store │ │ │ ├── index.ts │ │ │ └── rootReducer.ts │ ├── assets │ │ ├── fonts │ │ │ └── .gitkeep │ │ ├── icons │ │ │ ├── chevron-in-circle.svg │ │ │ ├── emailIcon.tsx │ │ │ ├── githubIcon.tsx │ │ │ ├── linkedinIcon.tsx │ │ │ └── twitterIcon.tsx │ │ ├── images │ │ │ ├── busy-sea-otters.png │ │ │ ├── footer.png │ │ │ ├── lightning-bolt.png │ │ │ ├── not-found-otters.svg │ │ │ ├── otter-floating.svg │ │ │ ├── rotational-ipn.png │ │ │ ├── rotational.svg │ │ │ ├── tileable-hexagon.png │ │ │ └── world-icon.png │ │ └── scss │ │ │ └── .gitkeep │ ├── components │ │ ├── Error │ │ │ ├── 404.tsx │ │ │ ├── ErrorPage │ │ │ │ ├── ErrorPage.tsx │ │ │ │ └── index.ts │ │ │ ├── Fallback.tsx │ │ │ ├── RTKErrorBoundary.tsx │ │ │ ├── SendReport.tsx │ │ │ ├── SentryErrorBoundary.tsx │ │ │ └── index.ts │ │ ├── GaWrapper │ │ │ └── index.tsx │ │ ├── MaintenanceMode │ │ │ ├── MaintenanceMode.stories.tsx │ │ │ ├── MaintenanceMode.tsx │ │ │ └── index.ts │ │ ├── MenuDropdown │ │ │ ├── MenuDropdown.tsx │ │ │ ├── OrganizationMenuDropdown.tsx │ │ │ └── useDropdownMenu.tsx │ │ ├── PasswordStrength │ │ │ ├── PasswordStrength.stories.tsx │ │ │ ├── PasswordStrength.tsx │ │ │ └── index.ts │ │ ├── ScheduleOfficeHours │ │ │ └── ScheduleOfficeHours.tsx │ │ ├── __tests__ │ │ │ └── .gitkeep │ │ ├── auth │ │ │ ├── LandingFooter │ │ │ │ ├── LandingFooter.stories.tsx │ │ │ │ ├── LandingFooter.tsx │ │ │ │ └── index.ts │ │ │ └── LandingHeader │ │ │ │ ├── Landing.spec.tsx │ │ │ │ ├── LandingHeader.stories.tsx │ │ │ │ ├── LandingHeader.tsx │ │ │ │ └── index.ts │ │ ├── common │ │ │ ├── Alert │ │ │ │ └── Alert.tsx │ │ │ ├── CardListItem │ │ │ │ ├── CardListItem.stories.tsx │ │ │ │ ├── CardListItem.tsx │ │ │ │ └── index.ts │ │ │ ├── Checkbox │ │ │ │ └── Checkbox.tsx │ │ │ ├── FilterTable │ │ │ │ ├── FilterTable.tsx │ │ │ │ └── index.tsx │ │ │ ├── Formik │ │ │ │ └── FormDebug.tsx │ │ │ ├── Logo.tsx │ │ │ ├── Modal │ │ │ │ ├── ApiKeyModal │ │ │ │ │ ├── ApiKeyModal.stories.tsx │ │ │ │ │ ├── ApiKeyModal.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── BCModalVideos.tsx │ │ │ ├── ProfileCard │ │ │ │ └── ProfileCard.tsx │ │ │ ├── QuickView │ │ │ │ ├── QuickView.stories.tsx │ │ │ │ ├── QuickView.tsx │ │ │ │ ├── QuickViewCard.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ └── QuickView.spec.tsx │ │ │ │ └── index.ts │ │ │ ├── TableHeader │ │ │ │ ├── TableHeading.stories.tsx │ │ │ │ ├── TableHeading.tsx │ │ │ │ └── index.tsx │ │ │ ├── TagState │ │ │ │ ├── TagState.tsx │ │ │ │ └── index.tsx │ │ │ └── Tooltip │ │ │ │ ├── HelpTooltip.tsx │ │ │ │ └── IconTooltip.tsx │ │ ├── icons │ │ │ ├── Icons.tsx │ │ │ ├── arrow-down-up.tsx │ │ │ ├── blueBars.tsx │ │ │ ├── check-circle.tsx │ │ │ ├── check.tsx │ │ │ ├── chevron-down.tsx │ │ │ ├── chevron-in-circle.tsx │ │ │ ├── close.tsx │ │ │ ├── closeEyeIcon.tsx │ │ │ ├── confirmedIndicatorIcon.tsx │ │ │ ├── copy-icon.tsx │ │ │ ├── docs.tsx │ │ │ ├── download-icon.tsx │ │ │ ├── eventing-icon.tsx │ │ │ ├── eventingIcon.svg │ │ │ ├── external-icon.tsx │ │ │ ├── folder.tsx │ │ │ ├── funnel-simple.tsx │ │ │ ├── hamburger.tsx │ │ │ ├── heavy-check-mark.tsx │ │ │ ├── help-icon.tsx │ │ │ ├── hint.tsx │ │ │ ├── home-icon.tsx │ │ │ ├── hosted-data-icon.tsx │ │ │ ├── lightning-bold-icon.tsx │ │ │ ├── onboarding-icon.tsx │ │ │ ├── onboarding-polygon.svg │ │ │ ├── openEyeIcon.tsx │ │ │ ├── otter-looking-down.tsx │ │ │ ├── oval.tsx │ │ │ ├── pendingIndicatorIcon.tsx │ │ │ ├── profile.tsx │ │ │ ├── refresh.tsx │ │ │ ├── revokedIndicatorIcon.tsx │ │ │ ├── setting.tsx │ │ │ ├── support.tsx │ │ │ ├── team.tsx │ │ │ ├── three-dots.tsx │ │ │ ├── union.tsx │ │ │ ├── unusedIndicatorIcon.tsx │ │ │ ├── white-heavy-check-mark.svg │ │ │ ├── world-icon.svg │ │ │ └── world-icon.tsx │ │ ├── layout │ │ │ ├── AppLayout.tsx │ │ │ ├── DashLayout.styles.tsx │ │ │ ├── DashLayout.tsx │ │ │ ├── MainLayout.tsx │ │ │ ├── MobileFooter │ │ │ │ ├── MobileFooter.tsx │ │ │ │ └── index.ts │ │ │ ├── MobileNav │ │ │ │ └── MobileNav.tsx │ │ │ ├── OnboardingLayout.tsx │ │ │ ├── ProfileAvatar │ │ │ │ └── ProfileAvatar.tsx │ │ │ ├── SanboxBanner │ │ │ │ └── SandboxBanner.tsx │ │ │ ├── SandboxLayout.tsx │ │ │ ├── Sidebar │ │ │ │ ├── OnboardingSideBar.tsx │ │ │ │ ├── SandboxSidebar.tsx │ │ │ │ ├── Sidebar.stories.tsx │ │ │ │ ├── Sidebar.style.css │ │ │ │ ├── Sidebar.tsx │ │ │ │ ├── UpdateAlert.tsx │ │ │ │ └── index.ts │ │ │ ├── Topbar │ │ │ │ ├── Topbar.tsx │ │ │ │ └── index.ts │ │ │ └── __tests__ │ │ │ │ ├── DashLayout.spec.tsx │ │ │ │ ├── OnboardingLayout.spec.tsx │ │ │ │ ├── SandboxLayout.spec.tsx │ │ │ │ ├── UpdateAlert.spec.tsx │ │ │ │ └── __snapshots__ │ │ │ │ ├── DashLayout.spec.tsx.snap │ │ │ │ ├── OnboardingLayout.spec.tsx.snap │ │ │ │ ├── SandboxLayout.spec.tsx.snap │ │ │ │ └── UpdateAlert.spec.tsx.snap │ │ └── ui │ │ │ ├── AccessDashboard │ │ │ ├── AccessDashboard.stories.tsx │ │ │ └── AccessDashboard.tsx │ │ │ ├── Avatar │ │ │ ├── Avatar.stories.tsx │ │ │ ├── Avatar.styles.tsx │ │ │ ├── Avatar.tsx │ │ │ ├── Avatar.type.ts │ │ │ └── index.ts │ │ │ ├── Breadcrumbs │ │ │ ├── Breadcrumbs.stories.tsx │ │ │ ├── Breadcrumbs.tsx │ │ │ ├── breadcrumbs-icon.svg │ │ │ ├── breadcrumbs-icon.tsx │ │ │ └── index.ts │ │ │ ├── CollapsibleMenu.tsx │ │ │ ├── Copy │ │ │ ├── Copy.tsx │ │ │ └── index.ts │ │ │ ├── Drawer │ │ │ ├── Drawer.tsx │ │ │ └── index.ts │ │ │ ├── Dropdown │ │ │ ├── Dropdown.styles.tsx │ │ │ ├── Dropdown.tsx │ │ │ └── index.tsx │ │ │ ├── Image │ │ │ ├── Image.tsx │ │ │ └── index.ts │ │ │ ├── Link │ │ │ ├── Link.tsx │ │ │ └── index.ts │ │ │ ├── Loader │ │ │ ├── Loader.spec.tsx │ │ │ ├── Loader.stories.tsx │ │ │ ├── Loader.style.css │ │ │ ├── Loader.tsx │ │ │ └── index.ts │ │ │ ├── OvalLoader │ │ │ ├── OvalLoader.tsx │ │ │ └── index.ts │ │ │ ├── PasswordField │ │ │ ├── PasswordField.tsx │ │ │ ├── PasswordField.type.ts │ │ │ ├── index.ts │ │ │ └── usePasswordField.tsx │ │ │ ├── PasswordTooltip │ │ │ └── PasswordTooltip.tsx │ │ │ ├── Select │ │ │ ├── Select.tsx │ │ │ └── index.ts │ │ │ ├── Settings │ │ │ ├── Settings.tsx │ │ │ └── index.ts │ │ │ ├── Tag │ │ │ ├── Tag.stories.tsx │ │ │ ├── Tag.tsx │ │ │ └── index.ts │ │ │ ├── TextArea │ │ │ ├── TextArea.tsx │ │ │ └── index.tsx │ │ │ ├── TextField │ │ │ ├── TextField.tsx │ │ │ └── index.ts │ │ │ ├── Toast │ │ │ ├── Toast.tsx │ │ │ ├── Toast.types.ts │ │ │ ├── index.ts │ │ │ └── util.ts │ │ │ ├── Tooltip │ │ │ └── DetailTooltip.tsx │ │ │ └── index.ts │ ├── constants │ │ ├── app.ts │ │ ├── dashLayout.tsx │ │ ├── index.ts │ │ ├── lang-key.ts │ │ ├── mimeTypes.ts │ │ ├── queryKeys.ts │ │ └── rolesAndStatus.ts │ ├── contexts │ │ ├── .gitkeep │ │ ├── LanguageContext.tsx │ │ └── __tests__ │ │ │ └── .gitkeep │ ├── features │ │ ├── apiKeys │ │ │ ├── api │ │ │ │ ├── __tests__ │ │ │ │ │ ├── CreateApiKeyService.spec.ts │ │ │ │ │ └── deleteApiKey.spec.ts │ │ │ │ ├── apiKeysApiService.ts │ │ │ │ ├── createApiKey.ts │ │ │ │ └── deleteApiKeyApi.ts │ │ │ ├── components │ │ │ │ ├── GenerateAPIKeyForm.tsx │ │ │ │ └── GenerateAPIKeyModal.tsx │ │ │ ├── hooks │ │ │ │ ├── useCreateApiKey.ts │ │ │ │ ├── useDeleteApiKey.ts │ │ │ │ └── useFetchApiKeys.ts │ │ │ ├── schemas │ │ │ │ └── generateAPIKeyValidationSchema.ts │ │ │ └── types │ │ │ │ ├── apiKeyService.ts │ │ │ │ ├── createApiKeyService.ts │ │ │ │ └── deleteApiKeyService.ts │ │ ├── auth │ │ │ ├── api │ │ │ │ ├── ForgotPasswordApiService.ts │ │ │ │ ├── LoginApiService.ts │ │ │ │ ├── RegisterApiService.ts │ │ │ │ ├── ResetPasswordApi.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── ForgotPasswordApiService.spec.ts │ │ │ │ │ ├── LoginApiService.spec.ts │ │ │ │ │ ├── RegisterApiService.spec.ts │ │ │ │ │ └── ResetPasswordApi.spec.ts │ │ │ │ └── verifyTokenApiService.ts │ │ │ ├── components │ │ │ │ ├── Form │ │ │ │ │ ├── ForgotPasswordForm.tsx │ │ │ │ │ └── PasswordResetForm.tsx │ │ │ │ ├── LeftRegistrationPanel.tsx │ │ │ │ ├── Login │ │ │ │ │ ├── LoginForm.tsx │ │ │ │ │ ├── LoginFormValidation.ts │ │ │ │ │ └── __tests__ │ │ │ │ │ │ └── LoginForm.spec.tsx │ │ │ │ ├── LoginFooter.tsx │ │ │ │ ├── Register │ │ │ │ │ ├── RegistrationForm.tsx │ │ │ │ │ ├── SuccessfullAccountCreation.tsx │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── RegistrationForm.spec.tsx │ │ │ │ │ └── schemas │ │ │ │ │ │ └── registrationFormValidation.ts │ │ │ │ ├── Verify │ │ │ │ │ ├── Fail.tsx │ │ │ │ │ └── Success.tsx │ │ │ │ └── index.ts │ │ │ ├── hooks │ │ │ │ ├── useCheckVerifyToken.ts │ │ │ │ ├── useDisplayToast.ts │ │ │ │ ├── useForgotPassword.ts │ │ │ │ ├── useForgotPasswordForm.ts │ │ │ │ ├── useLogin.ts │ │ │ │ ├── usePasswordResetForm.tsx │ │ │ │ ├── useRegister.ts │ │ │ │ ├── useResetPassword.ts │ │ │ │ ├── useSubmitLogin.tsx │ │ │ │ └── useSubmitResetPassword.ts │ │ │ ├── index.ts │ │ │ ├── routes │ │ │ │ ├── ForgotPasswordPage.tsx │ │ │ │ ├── LoginPage.tsx │ │ │ │ ├── PasswordResetPage.tsx │ │ │ │ ├── RegistrationPage.tsx │ │ │ │ ├── ResetVerificationPage.tsx │ │ │ │ ├── VerifyPage.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── stories │ │ │ │ │ ├── Registration.stories.tsx │ │ │ │ │ └── SuccessfullAccountCreation.stories.tsx │ │ │ └── types │ │ │ │ ├── CreateAccountService.ts │ │ │ │ ├── ForgotPasswordService.ts │ │ │ │ ├── LoginFormService.ts │ │ │ │ ├── LoginService.ts │ │ │ │ ├── RegisterService.ts │ │ │ │ ├── ResetPasswordService.ts │ │ │ │ └── __tests__ │ │ │ │ ├── CreateAccountService.spec.ts │ │ │ │ └── RegisterService.spec.ts │ │ ├── home │ │ │ ├── api │ │ │ │ └── StatusApiService.ts │ │ │ ├── components │ │ │ │ ├── AccessDocumentationStep.tsx │ │ │ │ ├── ProjectDetails.stories.tsx │ │ │ │ ├── ProjectDetailsStep.tsx │ │ │ │ ├── QuickStart.tsx │ │ │ │ ├── QuickViewCard.tsx │ │ │ │ ├── QuickViewSummary.tsx │ │ │ │ ├── RevokeApiKeyModal.tsx │ │ │ │ ├── SetupNewProject.tsx │ │ │ │ ├── StarterVideos │ │ │ │ │ └── StarterVideo.tsx │ │ │ │ ├── Templates.tsx │ │ │ │ ├── WelcomeAttention.tsx │ │ │ │ └── stories │ │ │ │ │ ├── AccessDocumentation.stories.tsx │ │ │ │ │ └── RevokeApiKeyModal.stories.tsx │ │ │ ├── hooks │ │ │ │ ├── useCheckAttention.tsx │ │ │ │ └── useFetchStatus.ts │ │ │ ├── index.ts │ │ │ ├── routes │ │ │ │ └── Home.tsx │ │ │ ├── types │ │ │ │ └── StatusService.ts │ │ │ └── util │ │ │ │ ├── index.ts │ │ │ │ └── utils.tsx │ │ ├── members │ │ │ ├── api │ │ │ │ ├── _tests_ │ │ │ │ │ ├── createMemberApi.spec.ts │ │ │ │ │ ├── memberList.spec.ts │ │ │ │ │ └── updateMember.spec.ts │ │ │ │ ├── createMemberApiService.ts │ │ │ │ ├── deleteMemberApi.ts │ │ │ │ ├── getProfileAPI.ts │ │ │ │ ├── memberApiService.ts │ │ │ │ ├── memberListAPI.ts │ │ │ │ ├── updateMemberAPI.ts │ │ │ │ └── updateProfileAPI.ts │ │ │ ├── components │ │ │ │ ├── CancelModal │ │ │ │ │ ├── CancelAcctModal.stories.tsx │ │ │ │ │ ├── CancelAcctModal.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── ChangePassword │ │ │ │ │ ├── ChangePasswordForm.tsx │ │ │ │ │ └── ChangePasswordModal.tsx │ │ │ │ ├── MemberDetails.stories.tsx │ │ │ │ ├── MemberDetails.tsx │ │ │ │ └── MemberInfo.tsx │ │ │ ├── hooks │ │ │ │ ├── useCreateMember.ts │ │ │ │ ├── useDeleteMember.ts │ │ │ │ ├── useFetchMember.ts │ │ │ │ ├── useFetchMembers.ts │ │ │ │ ├── useFetchProfile.ts │ │ │ │ ├── useUpdateMember.tsx │ │ │ │ └── useUpdateProfile.tsx │ │ │ ├── routes │ │ │ │ └── MemberPage.tsx │ │ │ ├── types │ │ │ │ ├── addMemberFormService.ts │ │ │ │ ├── changePasswordFormService.ts │ │ │ │ ├── deleteMemberFormService.tsx │ │ │ │ ├── memberServices.ts │ │ │ │ └── profileService.ts │ │ │ └── utils.ts │ │ ├── misc │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ └── onboarding │ │ │ │ │ ├── SuccessfulTenantCreationModal.stories.tsx │ │ │ │ │ └── SuccessfulTenantCreationModal.tsx │ │ │ ├── index.ts │ │ │ └── routes │ │ │ │ └── .gitkeep │ │ ├── onboarding │ │ │ ├── components │ │ │ │ ├── OnboardingHeader.tsx │ │ │ │ ├── Step.tsx │ │ │ │ ├── StepButtons.tsx │ │ │ │ ├── stepper │ │ │ │ │ ├── Indicator.tsx │ │ │ │ │ ├── Stepper.tsx │ │ │ │ │ ├── StepperStep.tsx │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ ├── Stepper.spec.tsx │ │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ │ └── Stepper.spec.tsx.snap │ │ │ │ │ └── index.tsx │ │ │ │ └── steps │ │ │ │ │ ├── StepCounter.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── name │ │ │ │ │ ├── form.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── organization │ │ │ │ │ ├── form.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── preference │ │ │ │ │ ├── DeveloperSegment.tsx │ │ │ │ │ ├── form.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── profession │ │ │ │ │ │ ├── Header.tsx │ │ │ │ │ │ ├── ProfessionSegment.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── workspace │ │ │ │ │ ├── form.tsx │ │ │ │ │ └── index.tsx │ │ │ ├── hooks │ │ │ │ ├── useHandlePreviousBtn.tsx │ │ │ │ ├── useNameForm.tsx │ │ │ │ ├── useOrganizationForm.tsx │ │ │ │ ├── usePreferenceForm.tsx │ │ │ │ └── useWorkspaceForm.tsx │ │ │ ├── index.ts │ │ │ ├── layout │ │ │ │ └── index.tsx │ │ │ ├── routes │ │ │ │ └── OnboardingPage.tsx │ │ │ └── shared │ │ │ │ ├── constants.ts │ │ │ │ └── utils.ts │ │ ├── organization │ │ │ ├── api │ │ │ │ ├── _tests_ │ │ │ │ │ └── orgDetail.spec.ts │ │ │ │ ├── orgDetailApi.ts │ │ │ │ ├── organizationListApi.ts │ │ │ │ └── switchOrganizationApi.ts │ │ │ ├── components │ │ │ │ ├── DeleteOrgModal │ │ │ │ │ ├── DeleteOrgModal.stories.tsx │ │ │ │ │ ├── DeleteOrgModal.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── OrganizationDetails.tsx │ │ │ │ └── OrganizationTable.tsx │ │ │ ├── hooks │ │ │ │ ├── useFetchOrgDetail.ts │ │ │ │ ├── useFetchOrganizations.tsx │ │ │ │ └── useSwitchOrganization.ts │ │ │ ├── index.tsx │ │ │ ├── routes │ │ │ │ └── OrganizationPage.tsx │ │ │ ├── types │ │ │ │ ├── organizationService.ts │ │ │ │ └── switchService.ts │ │ │ └── utils.tsx │ │ ├── projects │ │ │ ├── api │ │ │ │ ├── __tests__ │ │ │ │ │ ├── createProject.spec.ts │ │ │ │ │ ├── createProjectTopic.spec.ts │ │ │ │ │ └── projectList.spec.ts │ │ │ │ ├── createProjectAPI.tsx │ │ │ │ ├── createTopicApiService.ts │ │ │ │ ├── projectDetailApiService.ts │ │ │ │ ├── projectListAPI.ts │ │ │ │ ├── projectQueryApiService.tsx │ │ │ │ ├── projectStatsApiService.ts │ │ │ │ └── updateProjectApiService.ts │ │ │ ├── components │ │ │ │ ├── APIKeysTable.tsx │ │ │ │ ├── ChangeOwner │ │ │ │ │ ├── ChangeOwnerForm.tsx │ │ │ │ │ ├── ChangeOwnerModal.tsx │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ ├── ChangeOwnerForm.spec.tsx │ │ │ │ │ │ └── ChangeOwnerModal.spec.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── DeleteProjectModal.tsx │ │ │ │ ├── EditProject │ │ │ │ │ ├── EditProjectForm.tsx │ │ │ │ │ ├── EditProjectModal.tsx │ │ │ │ │ ├── EditProjectModalForm.tsx │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ ├── EditProjectForm.spec.tsx │ │ │ │ │ │ └── EditProjectModal.spec.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── NewProject │ │ │ │ │ ├── NewProjectForm.tsx │ │ │ │ │ ├── NewProjectModal.tsx │ │ │ │ │ └── __tests__ │ │ │ │ │ │ └── NewProjectForm.spec.tsx │ │ │ │ ├── NewTopicModal.tsx │ │ │ │ ├── NewTopicModalForm.tsx │ │ │ │ ├── ProjectActive.tsx │ │ │ │ ├── ProjectBreadcrumbs.tsx │ │ │ │ ├── ProjectDetail.tsx │ │ │ │ ├── ProjectDetail │ │ │ │ │ ├── DetailInfo.tsx │ │ │ │ │ └── ProjectDetailHeader.tsx │ │ │ │ ├── ProjectDetailTooltip.tsx │ │ │ │ ├── ProjectList.tsx │ │ │ │ ├── ProjectSetup.tsx │ │ │ │ ├── ProjectsTable.tsx │ │ │ │ ├── Settings.tsx │ │ │ │ ├── TopicTable.tsx │ │ │ │ ├── TopicTableHeader.tsx │ │ │ │ └── __tests__ │ │ │ │ │ ├── ProjectList.spec.tsx │ │ │ │ │ ├── ProjectsTable.spec.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ └── ProjectsTable.spec.tsx.snap │ │ │ ├── hooks │ │ │ │ ├── useCreateProject.tsx │ │ │ │ ├── useCreateTopic.ts │ │ │ │ ├── useFetchProject.ts │ │ │ │ ├── useFetchProjectStats.ts │ │ │ │ ├── useFetchTenantProjects.ts │ │ │ │ ├── useProjectActive.ts │ │ │ │ ├── useProjectQuery.ts │ │ │ │ ├── useProjectSetup.ts │ │ │ │ └── useUpdateProject.tsx │ │ │ ├── index.ts │ │ │ ├── routes │ │ │ │ ├── ProjectDetailPage.tsx │ │ │ │ └── ProjectsPage.tsx │ │ │ ├── schemas │ │ │ │ └── createProjectTopicSchema.ts │ │ │ ├── types │ │ │ │ ├── Project.ts │ │ │ │ ├── createProjectService.ts │ │ │ │ ├── createTopicService.ts │ │ │ │ ├── generateAPIKeyService.ts │ │ │ │ ├── newProjectFormService.ts │ │ │ │ ├── projectQueryService.ts │ │ │ │ ├── projectService.ts │ │ │ │ ├── updateProjectFormService.ts │ │ │ │ ├── updateProjectOwnerFormService.ts │ │ │ │ └── updateProjectService.ts │ │ │ └── util.ts │ │ ├── sandbox │ │ │ ├── types │ │ │ │ └── accountType.ts │ │ │ └── util │ │ │ │ └── utils.ts │ │ ├── teams │ │ │ ├── api │ │ │ │ ├── getInviteTeamMemberRequest.ts │ │ │ │ └── invitationAuthenticationRequest.ts │ │ │ ├── components │ │ │ │ ├── AddNewMember │ │ │ │ │ ├── AddNewMemberForm.tsx │ │ │ │ │ └── AddNewMemberModal.tsx │ │ │ │ ├── ChangeRoleForm.tsx │ │ │ │ ├── ChangeRoleModal.tsx │ │ │ │ ├── DeleteMember │ │ │ │ │ ├── DeleteMemberForm.tsx │ │ │ │ │ ├── DeleteMemberModal.tsx │ │ │ │ │ └── __tests__ │ │ │ │ │ │ └── DeleteMemberForm.spec.tsx │ │ │ │ ├── ExistingUserInvitationPage.tsx │ │ │ │ ├── InviteTeamMemberVerification.tsx │ │ │ │ ├── NewUserInvitationPage.tsx │ │ │ │ ├── RegisterNewUser │ │ │ │ │ ├── NewInviteRegistrationForm.tsx │ │ │ │ │ └── schemas │ │ │ │ │ │ └── newInviteRegistrationFormValidation.ts │ │ │ │ ├── TeamInvitationCard.tsx │ │ │ │ ├── TeamsTable.tsx │ │ │ │ └── __tests__ │ │ │ │ │ └── TeamInvitationCard.spec.tsx │ │ │ ├── constants │ │ │ │ └── query-key.ts │ │ │ ├── hooks │ │ │ │ ├── useFetchInviteAuthentication.ts │ │ │ │ └── useUpdateMemberRole.ts │ │ │ ├── index.ts │ │ │ ├── loaders │ │ │ │ └── inviteTeamMember.ts │ │ │ ├── routes │ │ │ │ └── TeamsPage.tsx │ │ │ ├── types │ │ │ │ ├── changeRoleFormDto.ts │ │ │ │ ├── invites.ts │ │ │ │ └── member.ts │ │ │ └── util.ts │ │ ├── tenants │ │ │ ├── api │ │ │ │ ├── __tests__ │ │ │ │ │ └── tenantServices.spec.ts │ │ │ │ ├── createTenantApiService.ts │ │ │ │ └── tenantListAPI.ts │ │ │ ├── components │ │ │ │ ├── TenantTable.stories.tsx │ │ │ │ └── TenantTable.tsx │ │ │ ├── hooks │ │ │ │ ├── useCreateTenant.ts │ │ │ │ └── useFetchTenants.ts │ │ │ ├── types │ │ │ │ ├── createTenantService.ts │ │ │ │ └── tenantServices.ts │ │ │ └── utils │ │ │ │ └── tenant.ts │ │ └── topics │ │ │ ├── __mocks__ │ │ │ └── index.ts │ │ │ ├── api │ │ │ ├── __tests__ │ │ │ │ ├── topicStatsApiService.spec.ts │ │ │ │ └── topicsApiService.spec.ts │ │ │ ├── topicDetailApiService.ts │ │ │ ├── topicEventsApiService.ts │ │ │ ├── topicStatsApiService.ts │ │ │ └── topicsApiService.ts │ │ │ ├── components │ │ │ ├── AdvancedTopicPolicy.tsx │ │ │ ├── EventDetailTable.tsx │ │ │ ├── EventDetailTableHeader.tsx │ │ │ ├── Modal │ │ │ │ ├── ArchiveTopicModal.tsx │ │ │ │ ├── CloneTopicModal.tsx │ │ │ │ ├── DeleteTopicModal.tsx │ │ │ │ └── RevokeAPIKeyModal.tsx │ │ │ ├── QueryForm.tsx │ │ │ ├── TopicQuery.tsx │ │ │ ├── TopicQueryInfo.tsx │ │ │ ├── TopicQueryResult │ │ │ │ ├── DisplayResultData.tsx │ │ │ │ ├── MetaDataTable.tsx │ │ │ │ ├── MimeTypeResult │ │ │ │ │ ├── BinaryResult.tsx │ │ │ │ │ ├── HTMLResult.tsx │ │ │ │ │ ├── JSONResult.tsx │ │ │ │ │ ├── XMLResult.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── PaginatedViewButtons.tsx │ │ │ │ ├── QueryResultContent.tsx │ │ │ │ ├── ResultHeader.tsx │ │ │ │ ├── ViewingEvent.tsx │ │ │ │ └── index.tsx │ │ │ ├── TopicQuickView.tsx │ │ │ ├── TopicSettings.tsx │ │ │ ├── TopicStateTag.tsx │ │ │ ├── TopicsBreadcrumbs.tsx │ │ │ └── __tests__ │ │ │ │ ├── EventDetailTable.spec.tsx │ │ │ │ ├── TopicQueryResult.spec.tsx │ │ │ │ ├── TopicQuickView.spec.tsx │ │ │ │ └── __snapshots__ │ │ │ │ ├── EventDetailTable.spec.tsx.snap │ │ │ │ ├── TopicQueryResult.spec.tsx.snap │ │ │ │ └── TopicQuickView.spec.tsx.snap │ │ │ ├── hooks │ │ │ ├── useFetchTopic.ts │ │ │ ├── useFetchTopicEvents.ts │ │ │ ├── useFetchTopicStats.ts │ │ │ ├── useFetchTopics.ts │ │ │ ├── usePaginateTopicQuery.ts │ │ │ └── useTopicQueryInputForm.ts │ │ │ ├── index.tsx │ │ │ ├── routes │ │ │ └── TopicDetailPage.tsx │ │ │ ├── schemas │ │ │ └── topicQueryInputValidationSchema.ts │ │ │ ├── types │ │ │ ├── topicEventsService.ts │ │ │ └── topicService.ts │ │ │ └── utils.ts │ ├── hooks │ │ ├── __tests__ │ │ │ └── .gitkeep │ │ ├── useAccountType.ts │ │ ├── useAuth.ts │ │ ├── useBreadcrumbs.tsx │ │ ├── useDashOnboarding.ts │ │ ├── useDrawer.ts │ │ ├── useFetchPermissions │ │ │ ├── index.ts │ │ │ ├── permissionsApiService.ts │ │ │ └── useFetchPermissions.ts │ │ ├── useFetchQuickView │ │ │ ├── __tests__ │ │ │ │ └── quickViewApiService.spec.ts │ │ │ ├── index.ts │ │ │ ├── quickViewApiService.ts │ │ │ ├── quickViewService.ts │ │ │ └── useFetchQuickView.ts │ │ ├── useFocus.ts │ │ ├── useForgotPassword.ts │ │ ├── useGetCurrentTenant.tsx │ │ ├── usePermissions.tsx │ │ ├── useQueryParams.tsx │ │ ├── useResendEmail.ts │ │ ├── useResetPassword.ts │ │ ├── useRoles.ts │ │ └── useTracking.ts │ ├── index.css │ ├── lib │ │ ├── __tests__ │ │ │ └── .gitkeep │ │ ├── createGenericContext.tsx │ │ ├── index.ts │ │ └── validation.ts │ ├── locales │ │ ├── cs │ │ │ ├── messages.js │ │ │ └── messages.po │ │ ├── en │ │ │ ├── messages.js │ │ │ └── messages.po │ │ └── fr │ │ │ ├── messages.js │ │ │ └── messages.po │ ├── main.tsx │ ├── providers │ │ ├── .gitkeep │ │ ├── __tests__ │ │ │ └── .gitkeep │ │ └── index.tsx │ ├── store │ │ ├── .gitkeep │ │ ├── index.ts │ │ └── useOrgStore.ts │ ├── types │ │ ├── MenuItem.ts │ │ ├── app.d.ts │ │ ├── cypress-browserify-preprocessor.d.ts │ │ └── navigator.d.ts │ ├── utils │ │ ├── __mocks__ │ │ │ └── index.ts │ │ ├── __tests__ │ │ │ ├── inputSanitizer.spec.ts │ │ │ ├── password.spec.tsx │ │ │ └── slugifyDomain.test.ts │ │ ├── cookies.ts │ │ ├── decodeToken.ts │ │ ├── download-file.ts │ │ ├── error-message.ts │ │ ├── formatData.ts │ │ ├── formatDate.ts │ │ ├── getInitials.ts │ │ ├── inputSanitzer.ts │ │ ├── lazy-import.ts │ │ ├── logger.ts │ │ ├── misc.tsx │ │ ├── passwordChecker.ts │ │ ├── slugifyDomain.ts │ │ ├── strings.ts │ │ └── test-utils.tsx │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts ├── vitest.config.ts ├── vitest.setup.ts └── yarn.lock └── placeholder └── landing ├── favicon.png ├── index.html ├── otter-floating.svg └── rotational-logo.png /.dockerignore: -------------------------------------------------------------------------------- 1 | # Development 2 | .git 3 | .gitignore 4 | .github 5 | .vscode 6 | LICENSE 7 | README.md 8 | 9 | # OS Droppings 10 | .DS_Store 11 | 12 | # Node stuff 13 | node_modules 14 | build 15 | 16 | # Docker stuff 17 | .dockerignore 18 | Dockerfile 19 | Dockerfile.prod 20 | docker-compose.yaml 21 | 22 | # Local configuration 23 | fixtures 24 | .env -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # Code droppings 19 | .vscode/ 20 | 21 | # OS Droppings 22 | .DS_Store 23 | 24 | # Development Config 25 | .secret/ 26 | .env 27 | fixtures 28 | 29 | # Hugo generated files and build 30 | /docs/public/ 31 | /docs/resources/_gen/ 32 | /docs/assets/jsconfig.json 33 | hugo_stats.json 34 | /docs/.hugo_build.lock 35 | /docs/node_modules 36 | .history 37 | 38 | #frontend -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/themes/hugo-book"] 2 | path = docs/themes/hugo-book 3 | url = https://github.com/alex-shpak/hugo-book 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/sh 3 | . "$(dirname "$0")/_/husky.sh" 4 | cd web/beacon-app 5 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | cd web/beacon-app 5 | yarn lint-staged --quiet -------------------------------------------------------------------------------- /containers/beacon/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name localhost; 5 | 6 | location / { 7 | root /usr/share/nginx/html; 8 | index index.html index.htm; 9 | try_files $uri /index.html; 10 | } 11 | 12 | gzip on; 13 | gzip_vary on; 14 | gzip_min_length 10240; 15 | gzip_proxied expired no-cache no-store private auth; 16 | gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml; 17 | gzip_disable "MSIE [1-6]\."; 18 | } -------------------------------------------------------------------------------- /containers/ensign/.gitignore: -------------------------------------------------------------------------------- 1 | db/ -------------------------------------------------------------------------------- /containers/monitor/.gitignore: -------------------------------------------------------------------------------- 1 | grafana/* 2 | !grafana/dashboards/ -------------------------------------------------------------------------------- /containers/monitor/grafana/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/containers/monitor/grafana/.gitkeep -------------------------------------------------------------------------------- /containers/monitor/grafana/dashboards/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/containers/monitor/grafana/dashboards/.gitkeep -------------------------------------------------------------------------------- /containers/monitor/prometheus.yml: -------------------------------------------------------------------------------- 1 | # An example Prometheus configuration to scrape a single endpoint from docker-compose 2 | global: 3 | scrape_interval: 30s 4 | scrape_timeout: 10s 5 | 6 | scrape_configs: 7 | - job_name: 'ensign' 8 | scrape_interval: 15s 9 | scrape_timeout: 5s 10 | metrics_path: /metrics 11 | scheme: http 12 | static_configs: 13 | - targets: 14 | - 'ensign:1205' -------------------------------------------------------------------------------- /containers/placeholder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | COPY web/placeholder/landing/index.html /usr/share/nginx/html/ 4 | COPY web/placeholder/landing/favicon.png /usr/share/nginx/html/ 5 | COPY web/placeholder/landing/rotational-logo.png /usr/share/nginx/html/ 6 | COPY web/placeholder/landing/otter-floating.svg /usr/share/nginx/html/ -------------------------------------------------------------------------------- /containers/quarterdeck/.gitignore: -------------------------------------------------------------------------------- 1 | db/ 2 | emails/ -------------------------------------------------------------------------------- /containers/tenant/.gitignore: -------------------------------------------------------------------------------- 1 | db/ 2 | emails/ -------------------------------------------------------------------------------- /containers/tenant/db/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/containers/tenant/db/.gitkeep -------------------------------------------------------------------------------- /containers/uptime/.gitignore: -------------------------------------------------------------------------------- 1 | db/ 2 | uptime.tgz -------------------------------------------------------------------------------- /containers/uptime/db/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/containers/uptime/db/.gitkeep -------------------------------------------------------------------------------- /docs/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/assets/images/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/assets/images/banner.jpg -------------------------------------------------------------------------------- /docs/assets/images/cta-illustration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/assets/images/cta-illustration.jpg -------------------------------------------------------------------------------- /docs/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/assets/images/favicon.png -------------------------------------------------------------------------------- /docs/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/assets/images/logo.png -------------------------------------------------------------------------------- /docs/assets/scss/custom.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/assets/scss/custom.scss -------------------------------------------------------------------------------- /docs/config/_default/menus.en.toml: -------------------------------------------------------------------------------- 1 | ############## English navigation ############### 2 | # main menu 3 | [[main]] 4 | name = "Home" 5 | url = "" 6 | weight = 1 7 | 8 | [[main]] 9 | name = "FAQ" 10 | url = "/faq" 11 | weight = 2 12 | 13 | 14 | # footer menu 15 | [[footer]] 16 | name = "SDKs" 17 | url = "sdk/" 18 | weight = 1 19 | 20 | [[footer]] 21 | name = "Ensign Repo" 22 | url = "https://github.com/rotationalio/ensign" 23 | weight = 2 24 | 25 | [[footer]] 26 | name = "About Us" 27 | url = "https://rotational.io/about/" 28 | weight = 3 -------------------------------------------------------------------------------- /docs/config/_default/menus.fr.toml: -------------------------------------------------------------------------------- 1 | ############# French navigation ############## 2 | # main menu 3 | [[main]] 4 | [[main]] 5 | name = "Home" 6 | url = "" 7 | weight = 1 8 | 9 | [[main]] 10 | name = "FAQ" 11 | url = "faq" 12 | weight = 2 13 | 14 | # footer menu 15 | [[footer]] 16 | name = "Changelog" 17 | url = "changelog/" 18 | weight = 1 19 | 20 | [[footer]] 21 | name = "Contact" 22 | url = "contact/" 23 | weight = 2 24 | 25 | [[footer]] 26 | name = "Github" 27 | url = "https://github.com/themefisher/" 28 | weight = 3 -------------------------------------------------------------------------------- /docs/content/_index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Ensign Squad, Assemble!" 3 | date: 2023-05-16T11:16:03-04:00 4 | description: "Welcome to the Ensign docs!" 5 | --- 6 | 7 | Ensign is a new eventing tool that makes it fast, convenient, and fun to create event-driven microservices without needing a big team of devOps or platform engineers. 8 | 9 | Ensign is an eventing system distributed in time and space. It stores chronological changes to all objects, which you can query with SQL to get change vectors in addition to static snapshots. 10 | 11 | This is Ensign's developer documentation; we're glad you're here! 12 | 13 | {{< image src="img/ensign-squad.png" alt="A friendly group of cartoon sea otters collaborating on tough problems together" zoomable="true" >}} 14 | 15 | -------------------------------------------------------------------------------- /docs/content/ensql/_index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "EnSQL Reference" 3 | weight: 40 4 | date: 2023-07-29T12:03:04-05:00 5 | --- 6 | 7 | Ensign implements a lightweight structured query language called EnSQL that should be familiar to users of relational databases. The twist for Ensign is that EnSQL allows users to query an Ensign topic over specific windows of time to capture and filter events. While the base language will be familiar and easy to pick up if you've used ANSI or Postgres SQL in the past, there are a few differences and gotchas that are described in detail in this documentation! 8 | 9 | -------------------------------------------------------------------------------- /docs/content/ensql/syntax/images/SELECT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/content/ensql/syntax/images/SELECT.png -------------------------------------------------------------------------------- /docs/content/system/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Ensign Internals" 3 | weight: 90 4 | date: 2023-05-17T17:03:41-04:00 5 | --- 6 | 7 | This section of the documentation describes Ensign Core — the internals of the Ensign system. 8 | -------------------------------------------------------------------------------- /docs/i18n/en.yaml: -------------------------------------------------------------------------------- 1 | - id: search 2 | translation: Search 3 | 4 | - id: search_placeholder 5 | translation: Search Ensign Docs 6 | 7 | - id: topics 8 | translation: Browse Your Topics 9 | 10 | - id: first_name 11 | translation: First Name 12 | 13 | - id: last_name 14 | translation: Last Name 15 | 16 | - id: email 17 | translation: Email 18 | 19 | - id: contact_reason 20 | translation: Reason of Contact 21 | 22 | - id: write_message 23 | translation: Start Writing From Here 24 | 25 | - id: send 26 | translation: Send 27 | 28 | - id: search_result 29 | translation: Search result for -------------------------------------------------------------------------------- /docs/i18n/fr.yaml: -------------------------------------------------------------------------------- 1 | - id: search 2 | translation: Chercher 3 | 4 | - id: search_placeholder 5 | translation: Cherche Godocs ... 6 | 7 | - id: topics 8 | translation: Parcourez vos sujets 9 | 10 | - id: first_name 11 | translation: Prénom 12 | 13 | - id: last_name 14 | translation: Nom de famille 15 | 16 | - id: email 17 | translation: Email 18 | 19 | - id: contact_reason 20 | translation: Raison du contact 21 | 22 | - id: write_message 23 | translation: Commencez à écrire à partir d'ici 24 | 25 | - id: send 26 | translation: Envoyer 27 | 28 | - id: search_result 29 | translation: Résultat de recherche pour -------------------------------------------------------------------------------- /docs/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "exampleSite/public" 3 | command = "cd exampleSite && hugo --minify --gc --themesDir ../.." 4 | 5 | [build.environment] 6 | HUGO_VERSION = "0.109.0" 7 | GO_VERSION = "1.19.4" 8 | HUGO_THEME = "repo" 9 | HUGO_BASEURL = "/" 10 | 11 | [[headers]] 12 | for = "/*" # This defines which paths this specific [[headers]] block will cover. 13 | 14 | [headers.values] 15 | X-Frame-Options = "DENY" 16 | X-XSS-Protection = "1; mode=block" 17 | Referrer-Policy = "same-origin" 18 | Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload" 19 | -------------------------------------------------------------------------------- /docs/static/img/baleen_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/baleen_diagram.png -------------------------------------------------------------------------------- /docs/static/img/broker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/broker.png -------------------------------------------------------------------------------- /docs/static/img/comic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/comic.png -------------------------------------------------------------------------------- /docs/static/img/database_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/database_record.png -------------------------------------------------------------------------------- /docs/static/img/dbs_v_events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/dbs_v_events.png -------------------------------------------------------------------------------- /docs/static/img/detective.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/detective.png -------------------------------------------------------------------------------- /docs/static/img/ensign-squad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/ensign-squad.png -------------------------------------------------------------------------------- /docs/static/img/enson-laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/enson-laptop.png -------------------------------------------------------------------------------- /docs/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/favicon.png -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/logo.png -------------------------------------------------------------------------------- /docs/static/img/mascot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/mascot.jpg -------------------------------------------------------------------------------- /docs/static/img/sample-eda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/sample-eda.png -------------------------------------------------------------------------------- /docs/static/img/smol-laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/smol-laptop.png -------------------------------------------------------------------------------- /docs/static/img/weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/static/img/weather.png -------------------------------------------------------------------------------- /docs/themes/godocs-2/.editorconfig: -------------------------------------------------------------------------------- 1 | ; https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /docs/themes/godocs-2/.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .dist 4 | .tmp 5 | .lock 6 | .sass-cache 7 | npm-debug.log 8 | node_modules 9 | builds 10 | package-lock.json 11 | public 12 | resources 13 | .hugo_build.lock 14 | jsconfig.json 15 | hugo_stats.json 16 | go.sum 17 | yarn.lock -------------------------------------------------------------------------------- /docs/themes/godocs-2/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 8 3 | } 4 | -------------------------------------------------------------------------------- /docs/themes/godocs-2/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": ["*.html"], 5 | "options": { 6 | "parser": "go-template", 7 | "goTemplateBracketSpacing": true, 8 | "bracketSameLine": true 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /docs/themes/godocs-2/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/themes/godocs-2/assets/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin mobile-xs{ 2 | @media(max-width:400px){ 3 | @content; 4 | } 5 | } 6 | @mixin mobile{ 7 | @media(max-width:575px){ 8 | @content; 9 | } 10 | } 11 | @mixin tablet{ 12 | @media(max-width:767px){ 13 | @content; 14 | } 15 | } 16 | @mixin desktop{ 17 | @media(max-width:991px){ 18 | @content; 19 | } 20 | } 21 | @mixin desktop-lg{ 22 | @media(max-width:1200px){ 23 | @content; 24 | } 25 | } 26 | 27 | @mixin size($size){ 28 | width: $size; height: $size; 29 | } -------------------------------------------------------------------------------- /docs/themes/godocs-2/config.toml: -------------------------------------------------------------------------------- 1 | # DON'T REMOVE THIS FILE. 2 | # This file is only for forestry and blogdown, If you want to change the variables, please edit `exampleSite/config.toml` 3 | -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/404.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/themes/godocs-2/layouts/404.html -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/_default/changelog.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | 3 |
4 |
5 |
6 |
7 |
8 |

{{ .Title }}

9 |

{{.Params.Description}}

10 |
{{ .Content }}
11 |
12 |
13 |
14 |
15 |
16 | 17 | {{ end }} -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/_default/index.json: -------------------------------------------------------------------------------- 1 | {{ $index := slice }} 2 | {{ range .Site.RegularPages }} 3 | {{ $index = $index | append (dict "title" .Title "section" .Section "description" .Params.description "categories" .Params.categories "content" .Plain "href" .Permalink) }} 4 | {{ end }} 5 | {{ $index | jsonify }} -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/_default/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | 3 | {{ partial "default.html" . }} 4 | 5 | {{ end }} -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | 3 | {{ partial "default.html" . }} 4 | 5 | {{ end }} -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/index.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | 3 | {{ partial "default.html" . }} 4 | 5 | {{ end }} -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/partials/components/page-header.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/docs/themes/godocs-2/layouts/partials/components/page-header.html -------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/shortcodes/changelog.html: -------------------------------------------------------------------------------- 1 | {{ $_hugo_config := `{ "version": 1 }` }} 2 | 3 |
4 |
{{ .Get 0 | title }}
5 | {{ .Inner | markdownify }} 6 |
-------------------------------------------------------------------------------- /docs/themes/godocs-2/layouts/shortcodes/faq.html: -------------------------------------------------------------------------------- 1 | {{ $_hugo_config := `{ "version": 1 }` }} 2 | 3 |
4 |
5 |
6 |

{{ .Get 0 | markdownify }}

7 |

{{ .Inner | markdownify }}

8 |
9 |
10 |
-------------------------------------------------------------------------------- /docs/themes/godocs-2/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "exampleSite/public" 3 | command = "cd exampleSite && hugo --minify --gc --themesDir ../.." 4 | 5 | [build.environment] 6 | HUGO_VERSION = "0.109.0" 7 | GO_VERSION = "1.19.4" 8 | HUGO_THEME = "repo" 9 | HUGO_BASEURL = "/" 10 | 11 | [[headers]] 12 | for = "/*" # This defines which paths this specific [[headers]] block will cover. 13 | 14 | [headers.values] 15 | X-Frame-Options = "DENY" 16 | X-XSS-Protection = "1; mode=block" 17 | Referrer-Policy = "same-origin" 18 | Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload" 19 | -------------------------------------------------------------------------------- /pkg/ensign/api/generate.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | //go:generate bash generate.sh 4 | -------------------------------------------------------------------------------- /pkg/ensign/api/v1beta1/groups.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/twmb/murmur3" 5 | ) 6 | 7 | func (c *ConsumerGroup) Key() ([16]byte, error) { 8 | key := [16]byte{} 9 | 10 | // If the ID is already 16 bytes, use it directly without hashing. 11 | if len(c.Id) == 16 { 12 | copy(key[:], c.Id) 13 | return key, nil 14 | } 15 | 16 | // Hash the ID or the name to get the key 17 | hash := murmur3.New128() 18 | switch { 19 | case len(c.Id) > 0: 20 | hash.Write(c.Id) 21 | case c.Name != "": 22 | hash.Write([]byte(c.Name)) 23 | default: 24 | return key, ErrNoGroupID 25 | } 26 | 27 | copy(key[:], hash.Sum(nil)) 28 | return key, nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/ensign/broker/errors.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrBrokerNotRunning = errors.New("operation could not be completed: broker is not running") 7 | ErrUnknownID = errors.New("no publisher or subscriber registered with specified id") 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/ensign/buffer/errors.go: -------------------------------------------------------------------------------- 1 | package buffer 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrBufferFull = errors.New("cannot write event: buffer is full") 7 | ErrBufferEmpty = errors.New("cannot read event: buffer is empty") 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/ensign/config/testdata/config.yaml: -------------------------------------------------------------------------------- 1 | maintenance: true 2 | log_level: "warn" 3 | console_log: true 4 | bind_addr: "127.0.0.1:7778" 5 | monitoring: 6 | enabled: true 7 | bind_addr: "127.0.0.1:7779" 8 | node: "ensign-1" 9 | storage: 10 | read_only: false 11 | data_path: /hello/world -------------------------------------------------------------------------------- /pkg/ensign/config/testdata/partial.yaml: -------------------------------------------------------------------------------- 1 | console_log: true 2 | bind_addr: "127.0.0.1:7778" -------------------------------------------------------------------------------- /pkg/ensign/contexts/errors.go: -------------------------------------------------------------------------------- 1 | package contexts 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNoClaimsInContext = errors.New("no claims available in context") 7 | ErrNotAuthorized = errors.New("claims do not have required permission") 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/ensign/contexts/stream.go: -------------------------------------------------------------------------------- 1 | package contexts 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | // Stream allows users to override the context on a grpc.ServerStream handler so that 10 | // it returns a new context rather than the old context. It is advised to use the 11 | // original stream's context as the new context's parent but this method does not 12 | // enforce it and instead simply returns the context specified. 13 | func Stream(s grpc.ServerStream, ctx context.Context) grpc.ServerStream { 14 | return &stream{s, ctx} 15 | } 16 | 17 | type stream struct { 18 | grpc.ServerStream 19 | ctx context.Context 20 | } 21 | 22 | func (s *stream) Context() context.Context { 23 | return s.ctx 24 | } 25 | -------------------------------------------------------------------------------- /pkg/ensign/ensql/step.go: -------------------------------------------------------------------------------- 1 | package ensql 2 | 3 | type step uint16 4 | 5 | const ( 6 | stepInit step = iota 7 | stepTerm 8 | stepSelect 9 | stepSelectField 10 | stepSelectFieldAlias 11 | stepSelectFrom 12 | stepSelectFromSchema 13 | stepSelectFromVersion 14 | stepWhere 15 | stepWhereField 16 | stepWhereOperator 17 | stepWhereValue 18 | stepWhereLogical 19 | stepWhereCloseParens 20 | stepOffset 21 | stepOffsetValue 22 | stepLimit 23 | stepLimitValue 24 | ) 25 | -------------------------------------------------------------------------------- /pkg/ensign/interceptors/interceptors_test.go: -------------------------------------------------------------------------------- 1 | package interceptors_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/rotationalio/ensign/pkg/utils/logger" 8 | ) 9 | 10 | func TestMain(m *testing.M) { 11 | logger.Discard() 12 | exitVal := m.Run() 13 | logger.ResetLogger() 14 | os.Exit(exitVal) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/ensign/mimetype/generate.go: -------------------------------------------------------------------------------- 1 | package mimetype 2 | 3 | //go:generate bash generate.sh 4 | -------------------------------------------------------------------------------- /pkg/ensign/mimetype/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROTOS="${GOPATH}/src/github.com/rotationalio/ensign/proto" 4 | 5 | if [[ ! -d $PROTOS ]]; then 6 | echo "cannot find ${PROTOS}" 7 | exit 1 8 | fi 9 | 10 | MODULE="github.com/rotationalio/ensign/pkg/ensign/mimetype/v1beta1" 11 | MOD="github.com/rotationalio/ensign/pkg/ensign/mimetype/v1beta1;mimetype" 12 | 13 | # Generate the protocol buffers 14 | protoc -I=${PROTOS} \ 15 | --go_out=./v1beta1 \ 16 | --go_opt=module="${MODULE}" \ 17 | --go_opt=Mmimetype/v1beta1/mimetype.proto="${MOD}" \ 18 | mimetype/v1beta1/mimetype.proto 19 | -------------------------------------------------------------------------------- /pkg/ensign/mock/creds.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | type Credentials struct { 10 | token string 11 | } 12 | 13 | func (t *Credentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 14 | return map[string]string{ 15 | "Authorization": "Bearer " + t.token, 16 | }, nil 17 | } 18 | 19 | func (t *Credentials) RequireTransportSecurity() bool { 20 | return false 21 | } 22 | 23 | func PerRPCToken(token string) grpc.CallOption { 24 | return grpc.PerRPCCredentials(&Credentials{token: token}) 25 | } 26 | 27 | func WithPerRPCToken(token string) grpc.DialOption { 28 | return grpc.WithPerRPCCredentials(&Credentials{token: token}) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/ensign/region/generate.go: -------------------------------------------------------------------------------- 1 | package region 2 | 3 | //go:generate bash generate.sh 4 | -------------------------------------------------------------------------------- /pkg/ensign/region/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROTOS="${GOPATH}/src/github.com/rotationalio/ensign/proto" 4 | 5 | if [[ ! -d $PROTOS ]]; then 6 | echo "cannot find ${PROTOS}" 7 | exit 1 8 | fi 9 | 10 | MODULE="github.com/rotationalio/ensign/pkg/ensign/region/v1beta1" 11 | MOD="github.com/rotationalio/ensign/pkg/ensign/region/v1beta1;region" 12 | 13 | # Generate the protocol buffers 14 | protoc -I=${PROTOS} \ 15 | --go_out=./v1beta1 \ 16 | --go_opt=module="${MODULE}" \ 17 | --go_opt=Mregion/v1beta1/region.proto="${MOD}" \ 18 | region/v1beta1/region.proto 19 | -------------------------------------------------------------------------------- /pkg/ensign/rlid/sequence.go: -------------------------------------------------------------------------------- 1 | package rlid 2 | 3 | import "sync" 4 | 5 | // Sequence generates totally ordered RLIDs but is not thread-safe. 6 | type Sequence uint32 7 | 8 | func (s *Sequence) Next() RLID { 9 | *s++ 10 | return Make(uint32(*s)) 11 | } 12 | 13 | // LockedSequence generates thread-safe totally ordered RLIDs. 14 | type LockedSequence struct { 15 | sync.Mutex 16 | seq uint32 17 | } 18 | 19 | func (s *LockedSequence) Next() RLID { 20 | s.Lock() 21 | defer s.Unlock() 22 | s.seq++ 23 | return Make(s.seq) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/ensign/store/errors/iter.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | func NewIter(err error) ErrorIterator { 4 | return ErrorIterator{err: err} 5 | } 6 | 7 | // ErrorIterator implements the iterator.Iterator interface and can be used to return 8 | // errors from methods that need to return iterators. 9 | type ErrorIterator struct { 10 | err error 11 | } 12 | 13 | func (e ErrorIterator) Key() []byte { return nil } 14 | func (e ErrorIterator) Value() []byte { return nil } 15 | func (e ErrorIterator) Next() bool { return false } 16 | func (e ErrorIterator) Prev() bool { return false } 17 | func (e ErrorIterator) Error() error { return e.err } 18 | func (e ErrorIterator) Release() {} 19 | -------------------------------------------------------------------------------- /pkg/ensign/store/events/testdata/readonly.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/ensign/store/events/testdata/readonly.zip -------------------------------------------------------------------------------- /pkg/ensign/store/meta/testdata/readonly.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/ensign/store/meta/testdata/readonly.zip -------------------------------------------------------------------------------- /pkg/ensign/store/mock/mock_test.go: -------------------------------------------------------------------------------- 1 | package mock_test 2 | -------------------------------------------------------------------------------- /pkg/ensign/testdata/queries.txt: -------------------------------------------------------------------------------- 1 | 1,"SELECT DISTINCT FROM albums WHERE pid IN (S49se6A0jDYazozoQoVlVq, e6AsYazVS0jQolVqzoD49o);" 2 | 2,"SELECT * FROM albums ORDER BY year;" 3 | 3,"SELECT DISTINCT user FROM listens WHERE timestamp < 2022-05-11T13:00:00-04:00;" 4 | 4,"SELECT * FROM listens WHERE pid == lqQjVYoazoVse649A0ozDS AND track == 1;" 5 | 5,"SELECT albums.pid, albums.name FROM albums INNER JOIN listens ON albums.pid=listens.pid;" -------------------------------------------------------------------------------- /pkg/ensign/testdata/topic.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AYazS49oVlVqse6ozo0jDQ", 3 | "id_ulid": "01GTSMQ3V8ASAPNCFEN378T8RD", 4 | "project_id": "AYazSjAlFdJcNJqvmvlCOg", 5 | "project_id_ulid": "01GTSMMC152Q95RD4TNYDFJGHT", 6 | "name": "testing.testapp.test", 7 | "readonly": false, 8 | "offset": 83123, 9 | "status": "READY", 10 | "shards": 1, 11 | "placements": [], 12 | "types": [], 13 | "created": "2023-03-05T19:41:59.016422Z", 14 | "modified": "2023-03-10T12:14:01.942323Z" 15 | } -------------------------------------------------------------------------------- /pkg/quarterdeck/api/v1/creds.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // Credentials provides a basic interface for loading an access token from Quarterdeck 4 | // into the Quarterdeck API client. Credentials can be loaded from disk, generated, or 5 | // feched from a passthrough request. 6 | type Credentials interface { 7 | AccessToken() (string, error) 8 | } 9 | 10 | // A Token is just the JWT base64 encoded token string that is obtained from 11 | // Quarterdeck either using the authtest server or from a login with the client. 12 | type Token string 13 | 14 | // Token implements the credentials interface and performs limited validation. 15 | func (t Token) AccessToken() (string, error) { 16 | if string(t) == "" { 17 | return "", ErrInvalidCredentials 18 | } 19 | return string(t), nil 20 | } 21 | -------------------------------------------------------------------------------- /pkg/quarterdeck/api/v1/options.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // ClientOption allows us to configure the APIv1 client when it is created. 8 | type ClientOption func(c *APIv1) error 9 | 10 | func WithClient(client *http.Client) ClientOption { 11 | return func(c *APIv1) error { 12 | c.client = client 13 | return nil 14 | } 15 | } 16 | 17 | func WithCredentials(creds Credentials) ClientOption { 18 | return func(c *APIv1) error { 19 | c.creds = creds 20 | return nil 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/quarterdeck/api/v1/refresh.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // A Reauthenticator generates new access and refresh pair given a valid refresh token. 8 | type Reauthenticator interface { 9 | Refresh(context.Context, *RefreshRequest) (*LoginReply, error) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/quarterdeck/db/errors.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrMissingEmail = errors.New("email address is required") 7 | ErrMissingUserID = errors.New("user id is required") 8 | ErrTokenMissingEmail = errors.New("email verification token is missing email address") 9 | ErrTokenMissingUserID = errors.New("email verification token is missing user id") 10 | ErrTokenExpired = errors.New("email verification token has expired") 11 | ErrInvalidSecret = errors.New("invalid secret for email token verification") 12 | ErrTokenInvalid = errors.New("email verification token has invalid signature") 13 | ErrSQLite3Conn = errors.New("could not get sqlite3 connection for backups") 14 | ) 15 | -------------------------------------------------------------------------------- /pkg/quarterdeck/db/migrations/0004_user_invitations.sql: -------------------------------------------------------------------------------- 1 | -- Add an invitations table to support user invitations. 2 | BEGIN; 3 | 4 | CREATE TABLE IF NOT EXISTS user_invitations ( 5 | user_id BLOB NOT NULL, 6 | organization_id BLOB NOT NULL, 7 | role TEXT NOT NULL, 8 | email TEXT NOT NULL, 9 | expires TEXT NOT NULL, 10 | token TEXT NOT NULL UNIQUE, 11 | secret BLOB NOT NULL, 12 | created_by BLOB NOT NULL, 13 | created TEXT NOT NULL, 14 | modified TEXT NOT NULL, 15 | FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE, 16 | FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE CASCADE 17 | ); 18 | 19 | COMMIT; -------------------------------------------------------------------------------- /pkg/quarterdeck/db/migrations/0005_user_org_logins.sql: -------------------------------------------------------------------------------- 1 | -- Add columns to the organization_users table. 2 | 3 | BEGIN; 4 | 5 | -- Last login time for the user in the organization. 6 | ALTER TABLE organization_users ADD COLUMN last_login TEXT DEFAULT NULL; 7 | 8 | -- Delete confirmation token for the organization user. 9 | ALTER TABLE organization_users ADD COLUMN delete_confirmation_token TEXT DEFAULT NULL; 10 | 11 | COMMIT; -------------------------------------------------------------------------------- /pkg/quarterdeck/db/migrations/0006_revoked_timestamp.sql: -------------------------------------------------------------------------------- 1 | -- Add a column for tracking when an API key was revoked. 2 | 3 | BEGIN; 4 | 5 | ALTER TABLE revoked_api_keys ADD COLUMN revoked TEXT DEFAULT NULL; 6 | 7 | COMMIT; -------------------------------------------------------------------------------- /pkg/quarterdeck/db/migrations/0007_account_type.sql: -------------------------------------------------------------------------------- 1 | -- Add a column for tracking the user account type. 2 | 3 | BEGIN; 4 | 5 | ALTER TABLE users ADD COLUMN account_type TEXT DEFAULT 'sandbox'; 6 | 7 | COMMIT; -------------------------------------------------------------------------------- /pkg/quarterdeck/replica/query/generate.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | //go:generate protoc -I=$GOPATH/src/github.com/rotationalio/ensign/proto --go_out=. --go_opt=module=github.com/rotationalio/ensign/pkg/quarterdeck/replica/query quarterdeck/query/v1beta1/query.proto 4 | -------------------------------------------------------------------------------- /pkg/quarterdeck/report/errors.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrBeforeLastRun = errors.New("cannot schedule report before last run") 7 | ) 8 | -------------------------------------------------------------------------------- /pkg/quarterdeck/tokens/errors.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrCacheMiss = errors.New("requested key is not in the cache") 7 | ErrCacheExpired = errors.New("requested key is expired") 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/quarterdeck/tokens/mock.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | type MockValidator struct { 4 | OnVerify func(string) (*Claims, error) 5 | OnParse func(string) (*Claims, error) 6 | Calls map[string]int 7 | } 8 | 9 | var _ Validator = &MockValidator{} 10 | 11 | func (m *MockValidator) Verify(tks string) (*Claims, error) { 12 | m.incr("Verify") 13 | return m.OnVerify(tks) 14 | } 15 | 16 | func (m *MockValidator) Parse(tks string) (*Claims, error) { 17 | m.incr("Parse") 18 | return m.OnParse(tks) 19 | } 20 | 21 | func (m *MockValidator) incr(method string) { 22 | if m.Calls == nil { 23 | m.Calls = make(map[string]int) 24 | } 25 | m.Calls[method]++ 26 | } 27 | -------------------------------------------------------------------------------- /pkg/raft/api/generate.go: -------------------------------------------------------------------------------- 1 | package raft 2 | 3 | //go:generate protoc -I=$GOPATH/src/github.com/rotationalio/ensign/proto --go_out=. --go_opt=module=github.com/rotationalio/ensign/pkg/raft/api --go-grpc_out=. --go-grpc_opt=module=github.com/rotationalio/ensign/pkg/raft/api raft/v1beta1/log.proto raft/v1beta1/raft.proto 4 | -------------------------------------------------------------------------------- /pkg/raft/api/v1beta1/log.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // NullEntry is an empty entry that is appended to the log. 4 | var NullEntry = &LogEntry{Index: 0, Term: 0, Key: nil, Value: nil} 5 | 6 | // IsZero returns true if the entry is the null entry. 7 | func (e *LogEntry) IsZero() bool { 8 | return e.Index == 0 && e.Term == 0 && e.Key == nil && e.Value == nil 9 | } 10 | -------------------------------------------------------------------------------- /pkg/raft/errors.go: -------------------------------------------------------------------------------- 1 | package raft 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrCannotSetRunningState = errors.New("can only set the running state from the initialized state") 7 | ) 8 | -------------------------------------------------------------------------------- /pkg/raft/log/options.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Option func(l *Log) error 4 | 5 | func WithStateMachine(sm StateMachine) Option { 6 | return func(l *Log) error { 7 | l.sm = sm 8 | return nil 9 | } 10 | } 11 | 12 | func WithSync(sync Sync) Option { 13 | return func(l *Log) error { 14 | l.sync = sync 15 | return nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/raft/log/testdata/meta.pb.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_applied": 10, 3 | "commit_index": 5, 4 | "length": 10, 5 | "created": "2022-11-24T20:43:42.739133Z", 6 | "modified": "2022-11-24T20:44:15.970283Z" 7 | } -------------------------------------------------------------------------------- /pkg/raft/peers/testdata/quorum.foo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/raft/peers/testdata/quorum.foo -------------------------------------------------------------------------------- /pkg/raft/peers/testdata/quorum.json: -------------------------------------------------------------------------------- 1 | { 2 | "quorum_id": 42, 3 | "boostrap_leader": 13, 4 | "peers": [{ 5 | "peer_id": 13, 6 | "name": "alpha", 7 | "bind_addr": ":7000", 8 | "endpoint": "localhost:7000" 9 | }, { 10 | "peer_id": 21, 11 | "name": "bravo", 12 | "bind_addr": ":8000", 13 | "endpoint": "localhost:8000" 14 | }, { 15 | "peer_id": 58, 16 | "name": "charlie", 17 | "bind_addr": ":9000", 18 | "endpoint": "localhost:9000" 19 | }] 20 | } -------------------------------------------------------------------------------- /pkg/raft/peers/testdata/quorum.yaml: -------------------------------------------------------------------------------- 1 | quorum_id: 42 2 | bootstrap_leader: 13 3 | peers: 4 | - peer_id: 13 5 | name: alpha 6 | bind_addr: :7000 7 | endpoint: localhost:7000 8 | - peer_id: 21 9 | name: bravo 10 | bind_addr: :8000 11 | endpoint: localhost:8000 12 | - peer_id: 58 13 | name: charlie 14 | bind_addr: :9000 15 | endpoint: localhost:9000 -------------------------------------------------------------------------------- /pkg/raft/testdata/quorum.json: -------------------------------------------------------------------------------- 1 | { 2 | "quorum_id": 42, 3 | "boostrap_leader": 13, 4 | "peers": [{ 5 | "peer_id": 13, 6 | "name": "alpha", 7 | "bind_addr": ":7000", 8 | "endpoint": "localhost:7000" 9 | }, { 10 | "peer_id": 21, 11 | "name": "bravo", 12 | "bind_addr": ":8000", 13 | "endpoint": "localhost:8000" 14 | }, { 15 | "peer_id": 58, 16 | "name": "charlie", 17 | "bind_addr": ":9000", 18 | "endpoint": "localhost:9000" 19 | }] 20 | } -------------------------------------------------------------------------------- /pkg/tenant/api/v1/options.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "net/http" 4 | 5 | // ClientOptions allows us to configure the APIv1 client when it is created. 6 | type ClientOption func(c *APIv1) error 7 | 8 | func WithClient(client *http.Client) ClientOption { 9 | return func(c *APIv1) error { 10 | c.client = client 11 | return nil 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/tenant/db/testdata/tenant.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", 3 | "name": "example-staging", 4 | "environment_type": "prod", 5 | "created": "2022-11-16T16:58:07-06:00", 6 | "modified": "2022-11-16T16:58:07-06:00" 7 | } -------------------------------------------------------------------------------- /pkg/tenant/db/time.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "time" 4 | 5 | // Helper to convert a time.Time to an RFC3339Nano string for JSON serialization. 6 | func TimeToString(t time.Time) string { 7 | if t.IsZero() { 8 | return "" 9 | } 10 | return t.Format(time.RFC3339Nano) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/uptime/db/iface.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | type Model interface { 4 | Key() ([]byte, error) 5 | Unmarshal([]byte) error 6 | Marshal() ([]byte, error) 7 | } 8 | -------------------------------------------------------------------------------- /pkg/uptime/health/errors.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrTooManyRedirects = errors.New("too many redirects, reporting unhealthy server") 7 | ErrNoContent = errors.New("api response did not contain any content") 8 | ErrNoStatusResponse = errors.New("api response did not contain a status") 9 | ErrUnparsableStatus = errors.New("api response status is unparsable") 10 | ErrNoServiceID = errors.New("service id must be specified before status can be saved to db") 11 | ErrNoTimestamp = errors.New("the service status does not have a checked at timestamp") 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/uptime/health/version.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | type Versioned interface { 4 | Version() string 5 | } 6 | 7 | // Checks if the service statuses are versioned, and if so, if the version has changed. 8 | func VersionChanged(first, second ServiceStatus) bool { 9 | var ( 10 | ok bool 11 | firstv Versioned 12 | secondv Versioned 13 | ) 14 | 15 | if firstv, ok = first.(Versioned); !ok { 16 | return false 17 | } 18 | 19 | if secondv, ok = second.(Versioned); !ok { 20 | return false 21 | } 22 | 23 | return firstv.Version() != secondv.Version() 24 | } 25 | -------------------------------------------------------------------------------- /pkg/uptime/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/uptime/static/favicon.png -------------------------------------------------------------------------------- /pkg/uptime/static/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/uptime/static/logo-white.png -------------------------------------------------------------------------------- /pkg/uptime/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/uptime/static/logo.png -------------------------------------------------------------------------------- /pkg/uptime/templates/incidents.html: -------------------------------------------------------------------------------- 1 | {{ template "incidents" . }} 2 | 3 | {{ define "incidents" }} 4 | {{ range $i, $day := .IncidentHistory }} 5 |
6 |

{{ $day.Date.Format "January 02, 2006" }}

7 | {{ range $j, $incident := $day.Incidents }} 8 |
9 |

{{ $incident.Description }}

10 | {{ $incident.TimeFormat }} 11 |
12 | {{ else }} 13 |
14 |

No incidents detected.

15 |
16 | {{ end }} 17 |
18 | {{ end }} 19 | {{ end }} -------------------------------------------------------------------------------- /pkg/utils/backups/errors.go: -------------------------------------------------------------------------------- 1 | package backups 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNotEnabled = errors.New("the backup manager is not enabled") 7 | ErrTmpDirUnavailable = errors.New("cannot create temporary directories for backup") 8 | ErrInvalidStorageDSN = errors.New("could not parse storage dsn, specify scheme:///relative/path/") 9 | ErrNotADirectory = errors.New("incorrectly configured: backup storage is not a directory") 10 | ErrNilSQLite3Conn = errors.New("could not fetch underlying sqlite3 connection for backup") 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/utils/backups/testdata/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/utils/backups/testdata/.gitkeep -------------------------------------------------------------------------------- /pkg/utils/backups/testdata/leveldb.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/utils/backups/testdata/leveldb.tgz -------------------------------------------------------------------------------- /pkg/utils/backups/testdata/sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/utils/backups/testdata/sqlite.db -------------------------------------------------------------------------------- /pkg/utils/emails/.gitignore: -------------------------------------------------------------------------------- 1 | # Test artifacts 2 | fixtures/ 3 | testdata/eyeball* -------------------------------------------------------------------------------- /pkg/utils/emails/errors.go: -------------------------------------------------------------------------------- 1 | package emails 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrMissingSubject = errors.New("missing email subject") 7 | ErrMissingSender = errors.New("missing email sender") 8 | ErrMissingRecipient = errors.New("missing email recipient") 9 | ErrUnparsable = errors.New("could not parse email address") 10 | ) 11 | -------------------------------------------------------------------------------- /pkg/utils/emails/iface.go: -------------------------------------------------------------------------------- 1 | package emails 2 | 3 | import ( 4 | "github.com/sendgrid/rest" 5 | sgmail "github.com/sendgrid/sendgrid-go/helpers/mail" 6 | ) 7 | 8 | // SendGridClient is an interface that can be implemented by live email clients to send 9 | // real emails or by mock clients for testing. 10 | type SendGridClient interface { 11 | Send(email *sgmail.SGMailV3) (*rest.Response, error) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/utils/emails/subjects.go: -------------------------------------------------------------------------------- 1 | package emails 2 | 3 | // Email subject lines 4 | const ( 5 | WelcomeRE = "Welcome to Ensign!" 6 | VerifyEmailRE = "Please verify your email address to login to Ensign" 7 | InviteRE = "Join Your Teammate %s on Ensign!" 8 | PasswordResetRequestRE = "Ensign Password Reset - Action Required" 9 | PasswordResetSuccessRE = "Ensign Password Reset Confirmation" 10 | DailyUsersRE = "Daily PLG Report for %s: %s" 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/utils/emails/templates/invite.txt: -------------------------------------------------------------------------------- 1 | Join Your Team on Ensign 2 | 3 | {{ .InviterName }} has invited you to use Ensign with them, in a workspace called {{ .OrgName }}. 4 | 5 | Accept the invitation by clicking this link. 6 | 7 | {{ .InviteURL }} 8 | 9 | If you have any questions, please contact support@rotational.io. 10 | 11 | Thank you, 12 | 13 | The Ensign Team 14 | Rotational Labs -------------------------------------------------------------------------------- /pkg/utils/emails/templates/partials/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ block "title" . }}{{ end }} 7 | {{ template "style.html" . }} 8 | 9 | 10 | {{ block "preheader" . }}{{ end }} 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pkg/utils/emails/templates/partials/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/utils/emails/templates/password_reset_success.html: -------------------------------------------------------------------------------- 1 | {{ template "base.html" . }} 2 | 3 | {{ define "title" }}Your Ensign Password Has Been Reset{{ end }} 4 | {{ define "preheader" }}Confirming that your Ensign password has successfully been reset.{{ end }} 5 | 6 | {{ define "content" }} 7 |
8 |

Hello,

9 | 10 |

Your Ensign password has been successfully reset. If you requested this password reset then no further action is required.

11 | 12 |

If you did not request a password reset, please contact our Ensign Customer Support team immediately at support@rotational.io.

13 | 14 |

We appreciate your attention to account security.

15 | 16 |

Best regards,

The Ensign Team
Rotational Labs

17 |
18 | {{ end }} -------------------------------------------------------------------------------- /pkg/utils/emails/templates/password_reset_success.txt: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | Your Ensign password has been successfully reset. If you requested this password reset then no further action is required. 4 | 5 | If you did not request a password reset, please contact our Ensign Customer Support team immediately at support@rotational.io. 6 | 7 | We appreciate your attention to account security. 8 | 9 | Best regards, 10 | 11 | The Ensign Team 12 | Rotational Labs -------------------------------------------------------------------------------- /pkg/utils/emails/templates/verify_email.txt: -------------------------------------------------------------------------------- 1 | Hello {{ .FullName }}, 2 | 3 | Thank you for registering with Ensign by Rotational Labs! In order to ensure the security of your account, please verify your email address by clicking or copy and pasting the following link into your browser: 4 | 5 | {{ .VerifyURL }} 6 | 7 | If you are having trouble verifying your email address, please contact us at support@rotational.io. 8 | 9 | Thank you, 10 | The Ensign Team -------------------------------------------------------------------------------- /pkg/utils/emails/testdata/foo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/pkg/utils/emails/testdata/foo.zip -------------------------------------------------------------------------------- /pkg/utils/gravatar/gravatar_test.go: -------------------------------------------------------------------------------- 1 | package gravatar_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rotationalio/ensign/pkg/utils/gravatar" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestGravatar(t *testing.T) { 11 | email := "MyEmailAddress@example.com " 12 | url := gravatar.New(email, nil) 13 | require.Equal(t, "https://www.gravatar.com/avatar/0bc83cb571cd1c50ba6f3e8a78ef1346?d=identicon&r=pg&s=80", url) 14 | } 15 | 16 | func TestHash(t *testing.T) { 17 | // Test case from: https://en.gravatar.com/site/implement/hash/ 18 | input := "MyEmailAddress@example.com " 19 | expected := "0bc83cb571cd1c50ba6f3e8a78ef1346" 20 | require.Equal(t, expected, gravatar.Hash(input)) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/utils/logger/testing.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/rs/zerolog" 9 | "github.com/rs/zerolog/log" 10 | ) 11 | 12 | var ( 13 | mu sync.Mutex 14 | orig *zerolog.Logger 15 | ) 16 | 17 | func ResetLogger() { 18 | mu.Lock() 19 | defer mu.Unlock() 20 | if orig != nil { 21 | log.Logger = *orig 22 | } 23 | } 24 | 25 | func Testing(tb testing.TB) { 26 | mu.Lock() 27 | defer mu.Unlock() 28 | orig = &log.Logger 29 | log.Logger = log.Output(zerolog.NewTestWriter(tb)) 30 | } 31 | 32 | func Discard() { 33 | mu.Lock() 34 | defer mu.Unlock() 35 | orig = &log.Logger 36 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: io.Discard}) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/utils/mtls/errors.go: -------------------------------------------------------------------------------- 1 | package mtls 2 | 3 | import "errors" 4 | 5 | // Standard errors for error type checking 6 | var ( 7 | ErrPrivateKeyRequired = errors.New("provider must contain a private key to initialize TLS certs") 8 | ErrNoCertificates = errors.New("provider does not contain any certificates") 9 | ErrMissingKey = errors.New("provider does not contain a private key") 10 | ErrZipEmpty = errors.New("zip archive contains no providers") 11 | ErrZipTooMany = errors.New("multiple providers in zip, is this a provider pool?") 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/utils/mtls/pem/errors.go: -------------------------------------------------------------------------------- 1 | package pem 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrDecodePrivateKey = errors.New("could not decode PEM private key") 7 | ErrDecodePublicKey = errors.New("could not decode PEM public key") 8 | ErrDecodeCertificate = errors.New("could not decode PEM certificate") 9 | ErrDecodeCSR = errors.New("could not decode PEM certificate request") 10 | ) 11 | -------------------------------------------------------------------------------- /pkg/utils/mtls/provider_test.go: -------------------------------------------------------------------------------- 1 | package mtls_test 2 | -------------------------------------------------------------------------------- /pkg/utils/mtls/testdata/README.md: -------------------------------------------------------------------------------- 1 | # Test Data 2 | 3 | These fixtures were generated by the tests themselves. To regenerate the fixtures, 4 | simply delete them and run the tests and they will be regenerated. 5 | 6 | See the `checkFixtures` function and the `createGroupA` and `createGroupB` functions 7 | for more information on how to adapt or customize the fixtures. -------------------------------------------------------------------------------- /pkg/utils/pagination/generate.go: -------------------------------------------------------------------------------- 1 | package pagination 2 | 3 | //go:generate protoc -I=$GOPATH/src/github.com/rotationalio/ensign/proto --go_out=. --go_opt=module=github.com/rotationalio/ensign/pkg/utils/pagination pagination/pagination.proto 4 | -------------------------------------------------------------------------------- /pkg/utils/probez/generate.go: -------------------------------------------------------------------------------- 1 | package probez 2 | 3 | //go:generate protoc -I=$GOPATH/src/github.com/rotationalio/ensign/proto --go_out=. --go_opt=module=github.com/rotationalio/ensign/pkg/utils/probez --go-grpc_out=. --go-grpc_opt=module=github.com/rotationalio/ensign/pkg/utils/probez grpc/health/v1/health.proto 4 | -------------------------------------------------------------------------------- /pkg/utils/radish/config_test.go: -------------------------------------------------------------------------------- 1 | package radish_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/rotationalio/ensign/pkg/utils/radish" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestConfig(t *testing.T) { 11 | testCases := []struct { 12 | conf Config 13 | err error 14 | }{ 15 | {Config{}, ErrNoWorkers}, 16 | {Config{Workers: 4}, ErrNoServerName}, 17 | {Config{Workers: 4, ServerName: "radish"}, nil}, 18 | } 19 | 20 | for i, tc := range testCases { 21 | err := tc.conf.Validate() 22 | require.ErrorIs(t, err, tc.err, "test case %d failed", i) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/utils/radish/tasks_test.go: -------------------------------------------------------------------------------- 1 | package radish_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | type TestTask struct { 10 | failUntil int 11 | attempts int 12 | success bool 13 | wg *sync.WaitGroup 14 | } 15 | 16 | func (t *TestTask) Do(ctx context.Context) error { 17 | t.attempts++ 18 | if t.attempts < t.failUntil { 19 | t.success = false 20 | return fmt.Errorf("task errored on attempt %d", t.attempts) 21 | } 22 | 23 | t.success = true 24 | t.wg.Done() 25 | return nil 26 | } 27 | 28 | func (t *TestTask) String() string { 29 | return "test task" 30 | } 31 | -------------------------------------------------------------------------------- /pkg/utils/responses/marshal.go: -------------------------------------------------------------------------------- 1 | package responses 2 | 3 | import "encoding/json" 4 | 5 | // Remarshal JSON data into a more readable string. 6 | func RemarshalJSON(data []byte) (string, error) { 7 | var obj interface{} 8 | if err := json.Unmarshal(data, &obj); err != nil { 9 | return "", err 10 | } 11 | pretty, err := json.MarshalIndent(obj, "", " ") 12 | if err != nil { 13 | return "", err 14 | } 15 | return string(pretty), nil 16 | } -------------------------------------------------------------------------------- /pkg/utils/rows/rows.go: -------------------------------------------------------------------------------- 1 | package rows 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "text/tabwriter" 8 | ) 9 | 10 | type Writer interface { 11 | Write([]string) error 12 | } 13 | 14 | func NewTabRowWriter(w *tabwriter.Writer) Writer { 15 | if w == nil { 16 | w = tabwriter.NewWriter(os.Stdout, 1, 0, 4, ' ', 0) 17 | } 18 | return &TabRowWriter{*w} 19 | } 20 | 21 | type TabRowWriter struct { 22 | tabwriter.Writer 23 | } 24 | 25 | func (w *TabRowWriter) Write(record []string) error { 26 | fmt.Fprintln(&w.Writer, strings.Join(record, "\t")) 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/utils/sentry/error.go: -------------------------------------------------------------------------------- 1 | package sentry 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // A standardized error type for fingerprinting inside of Sentry. 9 | type ServiceError struct { 10 | msg string 11 | args []interface{} 12 | err error 13 | } 14 | 15 | func (e *ServiceError) Error() string { 16 | if e.msg == "" { 17 | return e.err.Error() 18 | } 19 | 20 | msg := fmt.Sprintf(e.msg, e.args...) 21 | return fmt.Sprintf("%s: %s", msg, e.err) 22 | } 23 | 24 | func (e *ServiceError) Is(target error) bool { 25 | return errors.Is(e.err, target) 26 | } 27 | 28 | func (e *ServiceError) Unwrap() error { 29 | return e.err 30 | } 31 | -------------------------------------------------------------------------------- /pkg/utils/service/error.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNoServiceRegistered = errors.New("no service has been registered with the server") 7 | ) 8 | -------------------------------------------------------------------------------- /proto/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/proto/.gitkeep -------------------------------------------------------------------------------- /proto/api/v1beta1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ensign.v1beta1; 4 | 5 | // Query represents a single EnSQL query with associated placeholder parameters. 6 | message Query { 7 | string query = 1; 8 | repeated Parameter params = 2; 9 | bool include_duplicates = 3; 10 | } 11 | 12 | // Parameter holds a primitive value for passing as a placeholder to a sqlite query. 13 | message Parameter { 14 | oneof value { 15 | sint64 i = 1; 16 | double d = 2; 17 | bool b = 3; 18 | bytes y = 4; 19 | string s = 5; 20 | } 21 | string name = 6; 22 | } 23 | 24 | // Explanation returns information about the plan for executing a query and approximate 25 | // results or errors that might be returned. 26 | message QueryExplanation {} -------------------------------------------------------------------------------- /proto/raft/v1beta1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package raft.v1beta1; 4 | option go_package = "github.com/rotationalio/ensign/pkg/raft/api/v1beta1;api"; 5 | 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | message LogEntry { 9 | uint64 index = 1; 10 | uint64 term = 2; 11 | bytes key = 3; 12 | bytes value = 4; 13 | } 14 | 15 | message LogMeta { 16 | uint64 last_applied = 1; 17 | uint64 commit_index = 2; 18 | uint64 length = 3; 19 | google.protobuf.Timestamp created = 4; 20 | google.protobuf.Timestamp modified = 5; 21 | google.protobuf.Timestamp snapshot = 6; 22 | } -------------------------------------------------------------------------------- /web/beacon-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"], 3 | "plugins": ["transform-class-properties", "istanbul"] 4 | } -------------------------------------------------------------------------------- /web/beacon-app/.env.template: -------------------------------------------------------------------------------- 1 | # Displays the build version (tag) and git revision for debugging 2 | REACT_APP_VERSION_NUMBER=v0.14.0-dev 3 | REACT_APP_GIT_REVISION= 4 | 5 | # Specifies connection information for the backend 6 | REACT_APP_QUARTERDECK_BASE_URL=http://localhost:8088/v1 7 | REACT_APP_TENANT_BASE_URL=http://localhost:8080/v1 8 | 9 | # Sentry tracing configuration 10 | REACT_APP_SENTRY_DSN 11 | REACT_APP_SENTRY_ENVIRONMENT=development 12 | REACT_APP_SENTRY_EVENT_ID 13 | 14 | # Google Analytics ID 15 | REACT_APP_ANALYTICS_ID 16 | 17 | # i18n debugging flag 18 | REACT_APP_USE_DASH_LOCALE=false -------------------------------------------------------------------------------- /web/beacon-app/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | 4 | vite* -------------------------------------------------------------------------------- /web/beacon-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # misc 27 | build 28 | 29 | coverage 30 | public/tailwind.css 31 | 32 | #src/locales/* 33 | 34 | # cypress specific - we going to ignore all the files for now and only commit the ones we need 35 | cypress/fixtures/* 36 | cypress/downloads/* 37 | cypress/videos/* 38 | cypress/screenshots/* 39 | cypress/reports/* 40 | .nyc_output -------------------------------------------------------------------------------- /web/beacon-app/.linguirc: -------------------------------------------------------------------------------- 1 | { 2 | "locales": [ 3 | "en", 4 | "cs", 5 | "fr" 6 | ], 7 | "sourceLocale": "en", 8 | "catalogs": [ 9 | { 10 | "path": "src/locales/{locale}/messages", 11 | "include": [ 12 | "src" 13 | ] 14 | } 15 | ], 16 | "format": "po", 17 | "rootDir": ".", 18 | "extractBabelOptions": { 19 | "presets": [ 20 | "@babel/preset-typescript" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/beacon-app/.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /web/beacon-app/.nyc_output/processinfo/index.json: -------------------------------------------------------------------------------- 1 | {"processes":{},"files":{},"externalIds":{}} -------------------------------------------------------------------------------- /web/beacon-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 100, 5 | "tabWidth": 2, 6 | "jsxSingleQuote": false, 7 | "arrowParents": "always", 8 | "endOfLine": "auto", 9 | "overrides": [{ 10 | "files": "*.json", 11 | "options": { 12 | "tabWidth": 2 13 | } 14 | }] 15 | } -------------------------------------------------------------------------------- /web/beacon-app/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /web/beacon-app/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { withRouter } from 'storybook-addon-react-router-v6'; 2 | 3 | export const decorators = [withRouter]; 4 | 5 | export const parameters = { 6 | actions: { argTypesRegex: '^on[A-Z].*' }, 7 | controls: { 8 | matchers: { 9 | color: /(background|color)$/i, 10 | date: /Date$/, 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/ExistingMember.feature: -------------------------------------------------------------------------------- 1 | Feature: Accept member invitation 2 | 3 | I want to accept member invitation 4 | 5 | Scenario: Existing user when he has already an account 6 | 7 | Given I've already an account 8 | Then I should display login page 9 | 10 | 11 | 12 | Scenario: Existing user when he hasn't an account 13 | 14 | Given I've not an account 15 | Then I should display registration page -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/ExistingMember/ExistingMemberHasAcount.ts: -------------------------------------------------------------------------------- 1 | import { Given, Then } from 'cypress-cucumber-preprocessor/steps'; 2 | 3 | Given("I've already an account", () => { 4 | cy.visit('invite?token=nBW0OQr8yQxRWxo4aVGPCTIBsEjgaXIH5dlnJ2IWzkU'); 5 | }); 6 | 7 | Then('I should display login page', () => { 8 | // eslint-disable-next-line testing-library/await-async-query, testing-library/prefer-screen-queries 9 | cy.findByText("You've Been Invited!").should('exist'); 10 | // cy.getBySel('cy.get('[data-testid="email"]')') 11 | cy.get('[data-testid="inviter_name"]').should('exist'); 12 | cy.get('[data-testid="org_name"]').should('exist'); 13 | cy.get('[data-testid="role"]').should('exist'); 14 | }); 15 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/ExistingMember/ExistingMemberHasNotAcount.ts: -------------------------------------------------------------------------------- 1 | import { Given, Then } from 'cypress-cucumber-preprocessor/steps'; 2 | 3 | Given("I've not an account", () => { 4 | cy.visit('/invite?token=L-2BLXocLL-wpe4yOcd_otf6d-vHt0Zs8wPGgqFQCJU'); 5 | }); 6 | 7 | Then('I should display registration page', () => { 8 | // eslint-disable-next-line testing-library/await-async-query, testing-library/prefer-screen-queries 9 | cy.findByText("You've Been Invited!").should('exist'); 10 | // cy.getBySel('cy.get('[data-testid="email"]')') 11 | cy.get('[data-testid="inviter_name"]').should('exist'); 12 | cy.get('[data-testid="org_name"]').should('exist'); 13 | cy.get('[data-testid="role"]').should('exist'); 14 | cy.get('input').should('have.length', 5); 15 | }); 16 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/ForgotPassword.feature: -------------------------------------------------------------------------------- 1 | Feature: Forgot Password 2 | 3 | I want to submit a request to reset my password 4 | 5 | Scenario: Completing the forgot password form 6 | 7 | Given I am on the login page 8 | When I click the forgot password link 9 | Then I should be directed to the forgot password page 10 | When I click the submit button without entering an email address 11 | Then I should see a message informing me that an email address is required 12 | When I enter an invalid email address 13 | Then I should see a message informing me that the email address is invalid 14 | When I enter a valid email address 15 | And I click the submit button 16 | Then I should be directed to the reset password verification page -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/GenerateAPIKey.feature: -------------------------------------------------------------------------------- 1 | Feature: Generate API Key 2 | 3 | I want to generate an API Key 4 | 5 | Scenario: Generating an API Key workflow 6 | 7 | Given I log in to Beacon 8 | When I'm logged in 9 | Then I should see the Create API Key button 10 | When I click the Create API Key button 11 | Then I should see the Generate API Key modal 12 | And I should complete the Generate API Key modal 13 | When I click the Generate API Key button 14 | Then I should see the API Key confirmation modal 15 | And I should be able to copy and download the client id and client secret 16 | When I confirm that I have saved the client id and client secret 17 | Then I should see a green check mark under the Create API Key button 18 | And I should not be able to create another API key from the home page -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/InviteMember.feature: -------------------------------------------------------------------------------- 1 | Feature: Invite member 2 | 3 | I want to be able to invite a new member 4 | 5 | Scenario: Login to Beacon App 6 | 7 | Given I'm logged in 8 | When I navigate to the team page 9 | And I click on invite team member button 10 | And Add the member email address 11 | Then I should see the invited user in my team list 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/Login.feature: -------------------------------------------------------------------------------- 1 | Feature: Log in user 2 | 3 | I want to be able to login in Beacon App 4 | 5 | Scenario: Login to Beacon App 6 | 7 | Given I open the login page 8 | 9 | When I fill my credentials and submit the login form 10 | 11 | Then I'm Logged In 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/OrganizationDashboard.feature: -------------------------------------------------------------------------------- 1 | Feature: Display correct information on organization dashboard 2 | 3 | Scenario: Data are accurate 4 | 5 | Given I'm logged in to Beacon UI 6 | When I go to the organization dashboard 7 | Then I should see correct data 8 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/ProjectDetail.feature: -------------------------------------------------------------------------------- 1 | Feature: Project Detail 2 | 3 | I want to navigate the project detail page 4 | 5 | Scenario: Navigating the Beacon project detail page 6 | Given I'm navigating to beacon app 7 | When I'm logged in 8 | And I Click on manage project button 9 | Then I'm redirected to project detail page 10 | Then I should see project detail -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/Registration.feature: -------------------------------------------------------------------------------- 1 | Feature: Register as new tenant 2 | 3 | I want to be able to register as new tenant to Beacon App 4 | 5 | Scenario: Register to Beacon App 6 | 7 | Given I open the registration page 8 | When I click the Create Free Account button 9 | Then I should see the form error messages 10 | When I complete the registration form 11 | And I submit the registration form 12 | Then I should see the verify account page 13 | And I should see my email address in the verification email message 14 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/e2e/UserProfile.feature: -------------------------------------------------------------------------------- 1 | Feature: User profile page 2 | 3 | I want to navigate to the user profile page 4 | 5 | Scenario: Navigating the user profile page 6 | 7 | Given I login to Beacon 8 | When I'm logged in 9 | And I click Profile 10 | Then I navigate to the profile page 11 | Then I should see the user profile 12 | And I should see the user data 13 | Then I should see the organizations table 14 | And I should see the organization data 15 | 16 | When I click the cancel account button 17 | Then I should see cancel account modal 18 | 19 | When I click the close button 20 | Then I should not see the cancel account modal 21 | 22 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import browserify from '@cypress/browserify-preprocessor'; 2 | import cucumber from 'cypress-cucumber-preprocessor'; 3 | 4 | // eslint-disable-next-line 5 | type EventListener = (eventName: string | symbol, listener: (...args: any[]) => void) => void; 6 | 7 | module.exports = (on: EventListener) => { 8 | const options = browserify.defaultOptions; 9 | 10 | options.browserifyOptions.plugin.unshift(['tsify', { project: '../../tsconfig.json' }]); 11 | 12 | on('file:preprocessor', cucumber(options)); 13 | }; 14 | -------------------------------------------------------------------------------- /web/beacon-app/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress", "node"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } -------------------------------------------------------------------------------- /web/beacon-app/lingui.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@lingui/conf').LinguiConfig} */ 2 | module.exports = { 3 | locales: ['en', 'cs', 'fr'], 4 | catalogs: [ 5 | { 6 | path: 'src/locales/{locale}/messages', 7 | include: ['src'], 8 | }, 9 | ], 10 | format: 'po', 11 | }; 12 | -------------------------------------------------------------------------------- /web/beacon-app/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /web/beacon-app/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /web/beacon-app/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /web/beacon-app/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/apple-touch-icon.png -------------------------------------------------------------------------------- /web/beacon-app/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /web/beacon-app/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/favicon-16x16.png -------------------------------------------------------------------------------- /web/beacon-app/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/favicon-32x32.png -------------------------------------------------------------------------------- /web/beacon-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/favicon.ico -------------------------------------------------------------------------------- /web/beacon-app/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/favicon.png -------------------------------------------------------------------------------- /web/beacon-app/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/logo.png -------------------------------------------------------------------------------- /web/beacon-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ensign", 3 | "name": "Ensign by Rotational Labs", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "/android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/android-chrome-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#ffffff", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /web/beacon-app/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/public/mstile-150x150.png -------------------------------------------------------------------------------- /web/beacon-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /web/beacon-app/setupTests.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import '@testing-library/jest-dom/extend-expect'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/I18n.ts: -------------------------------------------------------------------------------- 1 | import { i18n } from '@lingui/core'; 2 | import { en, fr } from 'make-plural/plurals'; 3 | 4 | export const locales = { 5 | en: 'English', 6 | fr: 'French', 7 | }; 8 | export const DEFAULT_LOCALE = 'en'; 9 | 10 | i18n.loadLocaleData({ 11 | en: { plurals: en }, 12 | fr: { plurals: fr }, 13 | }); 14 | 15 | /** 16 | * We do a dynamic import of just the catalog that we need 17 | * @param locale any locale string 18 | */ 19 | export async function dynamicActivate(locale = 'en') { 20 | const { messages } = await import(`./locales/${locale}/messages.po`); 21 | 22 | i18n.load(locale, messages); 23 | i18n.activate(locale); 24 | } 25 | -------------------------------------------------------------------------------- /web/beacon-app/src/application/config/appEnv.ts: -------------------------------------------------------------------------------- 1 | import appConfig from './appConfig'; 2 | export const isProdEnv = appConfig.nodeENV === 'production'; 3 | export const isDevEnv = appConfig.nodeENV === 'development'; 4 | export const isTestEnv = appConfig.nodeENV === 'test'; 5 | export const isStagingEnv = appConfig.nodeENV === 'staging'; 6 | -------------------------------------------------------------------------------- /web/beacon-app/src/application/config/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as appConfig } from './appConfig'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/application/config/sentry.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/react'; 2 | 3 | import appConfig from './appConfig'; 4 | 5 | const initSentry = () => { 6 | const dsn = appConfig.sentryDSN; 7 | const environment = appConfig.sentryENV ? appConfig.sentryENV : appConfig.nodeENV; 8 | if (dsn) { 9 | Sentry.init({ 10 | dsn: dsn, 11 | integrations: [Sentry.browserTracingIntegration()], 12 | environment: environment, 13 | tracesSampleRate: 1.0, 14 | }); 15 | 16 | // eslint-disable-next-line no-console 17 | console.info('sentry tracing initialized'); 18 | } else { 19 | // eslint-disable-next-line no-console 20 | console.warn('no sentry configuration available'); 21 | } 22 | }; 23 | 24 | export default initSentry; 25 | -------------------------------------------------------------------------------- /web/beacon-app/src/application/index.ts: -------------------------------------------------------------------------------- 1 | export * from './routes'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/application/routes/PublicRoutes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigate } from 'react-router-dom'; 3 | 4 | import MainLayout from '@/components/layout/MainLayout'; 5 | import { useAuth } from '@/hooks/useAuth'; 6 | 7 | function PublicRoutes() { 8 | const { isAuthenticated } = useAuth(); 9 | 10 | if (isAuthenticated) return ; 11 | 12 | return ; 13 | } 14 | 15 | export default PublicRoutes; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/application/routes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './paths'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/application/store/rootReducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from '@reduxjs/toolkit'; 2 | 3 | const rootReducer = combineReducers({ 4 | // Add reducers here\ 5 | }); 6 | 7 | export type RootState = ReturnType; 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /web/beacon-app/src/assets/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/fonts/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/assets/icons/chevron-in-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/assets/icons/emailIcon.tsx: -------------------------------------------------------------------------------- 1 | export default function EmailIcon() { 2 | return ( 3 | 11 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/assets/images/busy-sea-otters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/images/busy-sea-otters.png -------------------------------------------------------------------------------- /web/beacon-app/src/assets/images/footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/images/footer.png -------------------------------------------------------------------------------- /web/beacon-app/src/assets/images/lightning-bolt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/images/lightning-bolt.png -------------------------------------------------------------------------------- /web/beacon-app/src/assets/images/rotational-ipn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/images/rotational-ipn.png -------------------------------------------------------------------------------- /web/beacon-app/src/assets/images/tileable-hexagon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/images/tileable-hexagon.png -------------------------------------------------------------------------------- /web/beacon-app/src/assets/images/world-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/images/world-icon.png -------------------------------------------------------------------------------- /web/beacon-app/src/assets/scss/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/assets/scss/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/components/Error/404.tsx: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/Error/ErrorPage/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ErrorPage } from './ErrorPage'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/Error/Fallback.tsx: -------------------------------------------------------------------------------- 1 | import { Loader } from '@rotational/beacon-core'; 2 | const Fallback = () => { 3 | return ( 4 |
5 | 6 |
7 | ); 8 | }; 9 | 10 | export default Fallback; 11 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/Error/SendReport.tsx: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/react'; 2 | 3 | import { appConfig } from '@/application/config'; 4 | export const SendReport = () => Sentry.showReportDialog({ eventId: appConfig.sentryEventID }); 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/Error/SentryErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | export { ErrorBoundary as SentryErrorBoundary } from '@sentry/react'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/Error/index.ts: -------------------------------------------------------------------------------- 1 | export { default as RTKErrorBoundary } from './RTKErrorBoundary'; 2 | export * from './SentryErrorBoundary'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/GaWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactGA from 'react-ga4'; 3 | 4 | interface IProps { 5 | children: React.ReactNode; 6 | isInitialized: boolean; 7 | } 8 | 9 | const GoogleAnalyticsWrapper: React.FC = ({ children, isInitialized }) => { 10 | const pathname = window.location.href; 11 | const search = window.location.search; 12 | 13 | React.useEffect(() => { 14 | if (isInitialized) { 15 | // ReactGA.set({ page: location.pathname }); 16 | ReactGA.send({ hitType: 'pageview', page: pathname + search }); 17 | } 18 | }, [isInitialized, pathname, search]); 19 | 20 | return <>{children}; 21 | }; 22 | 23 | export default GoogleAnalyticsWrapper; 24 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/MaintenanceMode/MaintenanceMode.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import MaintenanceMode from './MaintenanceMode'; 4 | 5 | export default { 6 | title: '/component/MaintenanceMode', 7 | } as Meta; 8 | 9 | const Template: Story = (args) => ; 10 | 11 | export const Default = Template.bind({}); 12 | Default.args = {}; 13 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/MaintenanceMode/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MaintenanceMode } from './MaintenanceMode'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/PasswordStrength/PasswordStrength.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import PasswordStrength from './PasswordStrength'; 4 | 5 | interface Props { 6 | string: string; 7 | } 8 | 9 | export default { 10 | title: 'component/Common/PasswordStrength', 11 | component: PasswordStrength, 12 | } as Meta; 13 | 14 | const Template: Story = (args) => ; 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = { 18 | string: '1Password@', 19 | }; 20 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/PasswordStrength/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PasswordStrength } from './PasswordStrength'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/__tests__/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/components/__tests__/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/components/auth/LandingFooter/LandingFooter.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import LandingFooter from './LandingFooter'; 4 | 5 | export default { 6 | title: 'component/landing-page/Footer', 7 | component: LandingFooter, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = {}; 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/auth/LandingFooter/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LandingFooter } from './LandingFooter'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/auth/LandingHeader/Landing.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import { BrowserRouter } from 'react-router-dom'; 3 | 4 | import LandingHeader from './LandingHeader'; 5 | 6 | describe('LandingHeader', () => { 7 | it('renders the logo and links', () => { 8 | render(, { wrapper: BrowserRouter }); 9 | 10 | const logo = screen.getByTestId('logo'); 11 | expect(logo).toBeInTheDocument(); 12 | 13 | // const starterPlanLink = screen.getByText('Starter Plan'); 14 | // expect(starterPlanLink).toBeInTheDocument(); 15 | 16 | /* const upgradeButton = screen.getByText('Upgrade'); 17 | expect(upgradeButton).toBeInTheDocument(); */ 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/auth/LandingHeader/LandingHeader.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import { LandingHeader } from '.'; 4 | 5 | export default { 6 | title: 'component/landing-page/Header', 7 | } as Meta; 8 | 9 | const Template: Story = (args) => ; 10 | 11 | export const Default = Template.bind({}); 12 | Default.args = {}; 13 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/auth/LandingHeader/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LandingHeader } from './LandingHeader'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/Alert/Alert.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export interface AlertProps { 4 | children: ReactNode; 5 | } 6 | 7 | function Alert({ children }: AlertProps) { 8 | return <>{children}; 9 | } 10 | 11 | export default Alert; 12 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/CardListItem/index.ts: -------------------------------------------------------------------------------- 1 | export type { CardListItemProps } from './CardListItem'; 2 | export { default as CardListItem } from './CardListItem'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/Checkbox/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | export interface CheckboxProps { 2 | onClick?: () => void; 3 | containerClassName?: string; 4 | id?: string; 5 | label: string; 6 | dataCy?: string; 7 | } 8 | 9 | function Checkbox({ onClick, containerClassName, id, label, dataCy }: CheckboxProps) { 10 | return ( 11 |
12 | 19 | 22 |
23 | ); 24 | } 25 | 26 | export default Checkbox; 27 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/FilterTable/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as FilterTable } from './FilterTable'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | import { EXTERNAL_LINKS } from '@/application'; 4 | import RotationalLogo from '@/assets/images/rotational.svg'; 5 | 6 | function Logo() { 7 | return ( 8 | 9 |
10 | Rotational Labs 11 |

Rotational Labs

12 |
13 | 14 | ); 15 | } 16 | 17 | export default Logo; 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/Modal/ApiKeyModal/ApiKeyModal.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import ApiKeyModal, { ApiKeyModalProps } from './ApiKeyModal'; 4 | 5 | export default { 6 | title: 'beacon/ApiKeyModal', 7 | } as Meta; 8 | 9 | const Template: Story = (args) => ; 10 | 11 | export const Default = Template.bind({}); 12 | Default.args = { 13 | open: true, 14 | }; 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/Modal/ApiKeyModal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ApiKeyModal } from './ApiKeyModal'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/Modal/BCModalVideos.tsx: -------------------------------------------------------------------------------- 1 | import 'node_modules/react-modal-video/scss/modal-video.scss'; 2 | 3 | import React, { Fragment } from 'react'; 4 | import ModalVideo from 'react-modal-video'; 5 | interface Props { 6 | key?: string; 7 | videoId: string; 8 | channel?: string; // youtube, vimeo, etc. but let use the default youtube 9 | isOpen: boolean; 10 | onClose: () => void; 11 | } 12 | 13 | const BCModalVideo = ({ key, videoId, isOpen, onClose }: Props) => { 14 | const k = Math.floor(Math.random() * 1000); 15 | return ( 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default BCModalVideo; 23 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/QuickView/QuickView.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import { QuickView, QuickViewProps } from '.'; 4 | 5 | export default { 6 | title: 'component/common/QuickView', 7 | component: QuickView, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = { 14 | data: [ 15 | { 16 | name: 'Active Projects', 17 | value: 10, 18 | }, 19 | { 20 | name: 'Topics', 21 | value: 10, 22 | }, 23 | { 24 | name: 'API Keys', 25 | value: 10, 26 | }, 27 | { 28 | name: 'Data Storage (GB)', 29 | value: 10, 30 | }, 31 | ], 32 | }; 33 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/QuickView/index.ts: -------------------------------------------------------------------------------- 1 | export type { QuickViewProps } from './QuickView'; 2 | export { default as QuickView } from './QuickView'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/TableHeader/TableHeading.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import { TableHeading, TableHeadingProps } from '.'; 4 | 5 | export default { 6 | title: 'component/common/TableHeading', 7 | component: TableHeading, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | 14 | Default.args = { 15 | children: 'Table Heading', 16 | }; 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/TableHeader/TableHeading.tsx: -------------------------------------------------------------------------------- 1 | import { Heading } from '@rotational/beacon-core'; 2 | 3 | export interface TableHeadingProps extends React.HTMLAttributes { 4 | children: React.ReactNode; 5 | } 6 | 7 | const TableHeading = ({ children, ...rest }: TableHeadingProps) => { 8 | return ( 9 |
10 | 11 | {children} 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default TableHeading; 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/TableHeader/index.tsx: -------------------------------------------------------------------------------- 1 | export type { TableHeadingProps } from './TableHeading'; 2 | export { default as TableHeading } from './TableHeading'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/TagState/TagState.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | 3 | import { Tag } from '@/components/ui/Tag'; 4 | import { PROJECT_STATE } from '@/constants/rolesAndStatus'; 5 | 6 | interface TopicStateTagProps { 7 | status: string; 8 | colorScheme?: string; 9 | } 10 | 11 | // TODO: list all possible values of status 12 | const StateMap = { 13 | [PROJECT_STATE.ACTIVE]: 'success', 14 | [PROJECT_STATE.ARCHIVED]: 'error', 15 | [PROJECT_STATE.INCOMPLETE]: 'warning', 16 | } as const; 17 | 18 | const TagState = ({ status }: TopicStateTagProps) => { 19 | return ( 20 | 21 | {status} 22 | 23 | ); 24 | }; 25 | 26 | export default TagState; 27 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/common/TagState/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as TagState } from './TagState'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/blueBars.tsx: -------------------------------------------------------------------------------- 1 | export function BlueBars(props: React.SVGProps) { 2 | return ( 3 | 11 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/check-circle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function CheckCircleIcon(props: React.SVGProps) { 4 | return ( 5 | 21 | ); 22 | } 23 | 24 | export default CheckCircleIcon; 25 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/check.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Check(props: React.SVGProps) { 4 | return ( 5 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default Check; 21 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/chevron-in-circle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function ChevronInCircle() { 4 | return ( 5 | 6 | 11 | 18 | 19 | ); 20 | } 21 | 22 | export default ChevronInCircle; 23 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/close.tsx: -------------------------------------------------------------------------------- 1 | export function Close(props: React.SVGAttributes) { 2 | return ( 3 | 12 | 18 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/confirmedIndicatorIcon.tsx: -------------------------------------------------------------------------------- 1 | export default function ConfirmedIndicatorIcon(props: React.SVGProps) { 2 | return ( 3 | 11 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/external-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | function ExternalIcon({ className, ...props }: React.SVGProps) { 5 | return ( 6 | 15 | 20 | 21 | ); 22 | } 23 | 24 | export default ExternalIcon; 25 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/home-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function HomeIcon() { 4 | return ( 5 | 13 | 18 | 19 | ); 20 | } 21 | 22 | export default HomeIcon; 23 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/onboarding-icon.tsx: -------------------------------------------------------------------------------- 1 | export default function OnboardingIcon(props: React.SVGProps) { 2 | return ( 3 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/onboarding-polygon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/pendingIndicatorIcon.tsx: -------------------------------------------------------------------------------- 1 | export default function PendingIndicatorIcon(props: React.SVGProps) { 2 | return ( 3 | 11 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/profile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function ProfileIcon(props: React.SVGProps) { 4 | return ( 5 | 14 | 19 | 20 | ); 21 | } 22 | 23 | export default ProfileIcon; 24 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/refresh.tsx: -------------------------------------------------------------------------------- 1 | function RefreshIcon() { 2 | return ( 3 | 10 | 16 | 17 | ); 18 | } 19 | 20 | export default RefreshIcon; 21 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/revokedIndicatorIcon.tsx: -------------------------------------------------------------------------------- 1 | export default function RevokedIndicatorIcon(props: React.SVGProps) { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/unusedIndicatorIcon.tsx: -------------------------------------------------------------------------------- 1 | export default function UnusedIndicatorIcon(props: React.SVGProps) { 2 | return ( 3 | 11 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/icons/white-heavy-check-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/DashLayout.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import { TOPBAR_HEIGHT } from '@/constants/dashLayout'; 4 | 5 | export const MainStyle = styled.main` 6 | margin-top: ${TOPBAR_HEIGHT}px; 7 | `; 8 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@rotational/beacon-core'; 2 | import { Outlet } from 'react-router-dom'; 3 | 4 | import { LandingFooter } from '@/components/auth/LandingFooter'; 5 | import { LandingHeader } from '@/components/auth/LandingHeader'; 6 | 7 | const MainLayout = () => { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default MainLayout; 20 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/MobileFooter/MobileFooter.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | import { footerItems } from '@/constants/dashLayout'; 4 | 5 | function MobileFooter() { 6 | return ( 7 |
8 |
    9 | {footerItems.map((item) => ( 10 |
  • 11 | 12 | {item.name} 13 | 14 |
  • 15 | ))} 16 |
17 |
18 | ); 19 | } 20 | 21 | export default MobileFooter; 22 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/MobileFooter/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './MobileFooter'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/OnboardingLayout.tsx: -------------------------------------------------------------------------------- 1 | import { MainStyle } from './DashLayout.styles'; 2 | import MobileFooter from './MobileFooter'; 3 | import { OnBoardingSidebar } from './Sidebar'; 4 | 5 | type OnboardingLayoutProps = { 6 | children?: React.ReactNode; 7 | }; 8 | 9 | const OnboardingLayout: React.FC = ({ children }) => { 10 | return ( 11 |
12 | 13 | {children} 14 | 15 |
16 | ); 17 | }; 18 | 19 | export default OnboardingLayout; 20 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/ProfileAvatar/ProfileAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { getInitials } from '@/utils/getInitials'; 2 | 3 | type ProfileAvatar = { 4 | name: string; 5 | }; 6 | 7 | const ProfileAvatar = ({ name }: ProfileAvatar) => { 8 | // console.log('[] name', name); 9 | return ( 10 |
11 | {getInitials(name)} 12 |
13 | ); 14 | }; 15 | 16 | export default ProfileAvatar; 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/SandboxLayout.tsx: -------------------------------------------------------------------------------- 1 | import MobileFooter from './MobileFooter'; 2 | import SandboxSidebar from './Sidebar/SandboxSidebar'; 3 | 4 | type SandboxLayoutProps = { 5 | children?: React.ReactNode; 6 | }; 7 | 8 | function SandboxLayout({ children }: SandboxLayoutProps) { 9 | return ( 10 |
11 | 12 |
{children}
13 | 14 |
15 | ); 16 | } 17 | 18 | export default SandboxLayout; 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/Sidebar/Sidebar.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import SideBar from './Sidebar'; 4 | 5 | export default { 6 | title: 'component/layout/SideBar', 7 | component: SideBar, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ( 11 |
12 | 13 |
14 | ); 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = {}; 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/Sidebar/Sidebar.style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/components/layout/Sidebar/Sidebar.style.css -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/Sidebar/UpdateAlert.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { Button } from '@rotational/beacon-core'; 3 | 4 | function UpdateAlert() { 5 | return ( 6 | <> 7 |

8 | 9 | A new version of Ensign is available. Click "Update" to 10 | use the latest version. 11 | 12 |

13 |
14 | 21 |
22 | 23 | ); 24 | } 25 | 26 | export default UpdateAlert; 27 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/Sidebar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as OnBoardingSidebar } from './OnboardingSideBar'; 2 | export { default as Sidebar } from './Sidebar'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/Topbar/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Topbar'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/layout/__tests__/__snapshots__/UpdateAlert.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`UpdateAlert > should render if the version number returned from the useFetchStatus hook is different from the version number from the appConfig 1`] = ` 4 |
5 |

6 |

7 |
12 |
13 | `; 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/AccessDashboard/AccessDashboard.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import AccessDashboard from './AccessDashboard'; 4 | 5 | export default { 6 | title: 'ui/AccessDashboard', 7 | component: AccessDashboard, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = {}; 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/AccessDashboard/AccessDashboard.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import { PATH_DASHBOARD } from '@/application'; 5 | import HeavyCheckMark from '@/components/icons/heavy-check-mark'; 6 | 7 | function AccessDashboard() { 8 | return ( 9 |
10 | 11 |
12 | 13 | View/Edit 14 | 15 |
16 |
17 | ); 18 | } 19 | 20 | export default memo(AccessDashboard); 21 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Avatar/Avatar.type.ts: -------------------------------------------------------------------------------- 1 | import { AvatarFallbackProps, AvatarImageProps } from '@radix-ui/react-avatar'; 2 | import { SetRequired } from 'type-fest'; 3 | 4 | type FallbackProps = Omit; 5 | 6 | export type AvatarProps = { 7 | fallbackProps?: FallbackProps; 8 | } & SetRequired; 9 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Avatar/index.ts: -------------------------------------------------------------------------------- 1 | import Avatar from './Avatar'; 2 | 3 | export * from './Avatar.type'; 4 | export default Avatar; 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Breadcrumbs/Breadcrumbs.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import Breadcrumbs from './Breadcrumbs'; 4 | 5 | export default { 6 | title: 'ui/Breadcrumbs', 7 | component: Breadcrumbs, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ( 11 | 12 | Item 1 13 | Item 2 14 | Item 2 15 | 16 | ); 17 | 18 | export const Default = Template.bind({}); 19 | Default.args = {}; 20 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Breadcrumbs/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Breadcrumbs'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Copy/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Copy'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Drawer'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Dropdown/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { MenuUnstyled } from '@mui/base'; 2 | import React from 'react'; 3 | 4 | import { Popper, StyledListbox, StyledMenuItem } from './Dropdown.styles'; 5 | export interface MenuSectionProps { 6 | children: React.ReactNode; 7 | label: string; 8 | className?: string; 9 | } 10 | 11 | const Menu = (props: React.ComponentPropsWithoutRef) => { 12 | const { slots, ...rest } = props; 13 | return ; 14 | }; 15 | 16 | Menu.Item = StyledMenuItem; 17 | 18 | export default Menu; 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Dropdown/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Dropdown } from './Dropdown'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Image/Image.tsx: -------------------------------------------------------------------------------- 1 | // this image component is used to display images 2 | 3 | import React from 'react'; 4 | 5 | interface ImageProps { 6 | src: string; 7 | alt?: string; 8 | className?: string; 9 | } 10 | 11 | const Image: React.FC = ({ src, alt, className }) => { 12 | return {alt}; 13 | }; 14 | 15 | export default Image; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Image/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Image } from './Image'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Link/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Link } from './Link'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Loader/Loader.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import Loader from './Loader'; 4 | 5 | export default { 6 | title: 'ui/Loader', 7 | component: Loader, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = { 14 | label: 'Retrofitting DeLorean...Activating flux capacitor...Adding final coat of paint', 15 | }; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Loader/Loader.style.css: -------------------------------------------------------------------------------- 1 | #loading-spinner { 2 | animation: loading-spinner 1s linear infinite; 3 | } 4 | 5 | @keyframes loading-spinner { 6 | from { 7 | transform: rotate(0deg); 8 | } 9 | 10 | to { 11 | transform: rotate(360deg); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Loader/index.ts: -------------------------------------------------------------------------------- 1 | import Loader from './Loader'; 2 | 3 | export default Loader; 4 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/OvalLoader/OvalLoader.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | import Oval from '@/components/icons/oval'; 4 | 5 | type ContainerProps = { 6 | className?: string; 7 | style?: React.CSSProperties; 8 | }; 9 | 10 | type OvalLoaderProps = { 11 | containerProps?: ContainerProps; 12 | children?: ReactNode; 13 | } & React.SVGProps; 14 | 15 | function OvalLoader({ containerProps, children, ...rest }: OvalLoaderProps) { 16 | return ( 17 |
18 | 19 |

{children}

20 |
21 | ); 22 | } 23 | 24 | export default OvalLoader; 25 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/OvalLoader/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './OvalLoader'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/PasswordField/PasswordField.tsx: -------------------------------------------------------------------------------- 1 | import { TextField } from '@rotational/beacon-core'; 2 | 3 | import { PasswordFieldProps } from './PasswordField.type'; 4 | import { usePasswordField } from './usePasswordField'; 5 | 6 | const PasswordField = (props: PasswordFieldProps) => { 7 | const _props = usePasswordField(props); 8 | 9 | return ; 10 | }; 11 | 12 | export default PasswordField; 13 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/PasswordField/PasswordField.type.ts: -------------------------------------------------------------------------------- 1 | import { TextFieldProps } from '@rotational/beacon-core'; 2 | 3 | export type PasswordFieldProps = Partial>; 4 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/PasswordField/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './PasswordField'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Select/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Select'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Settings/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Settings'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Tag/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Tag } from './Tag'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/TextArea/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as TextArea } from './TextArea'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/TextField/TextField.tsx: -------------------------------------------------------------------------------- 1 | import { TextField } from '@rotational/beacon-core'; 2 | import styled from 'styled-components'; 3 | 4 | const StyledTextField = styled(TextField)` 5 | margin-top: 4px; 6 | `; 7 | 8 | export default StyledTextField; 9 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/TextField/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './TextField'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Toast/Toast.types.ts: -------------------------------------------------------------------------------- 1 | import { ToastProps as RadixToastProps } from '@radix-ui/react-toast'; 2 | export type ToastProps = { 3 | variant?: 'default' | 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'; 4 | size?: 'small' | 'medium' | 'large'; 5 | hasIcon?: boolean; 6 | icon?: React.ReactNode; 7 | [key: string]: any; 8 | title?: string; 9 | placement?: 'up' | 'down' | 'left' | 'right'; 10 | description?: string; 11 | children?: React.ReactNode; 12 | onClose?: () => void; 13 | }; 14 | 15 | export type ToastWithRadixProps = ToastProps & RadixToastProps; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/Toast/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toast } from './Toast'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/components/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Avatar'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app'; 2 | export * from './queryKeys'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/constants/lang-key.ts: -------------------------------------------------------------------------------- 1 | export const LANG_KEY = 'ensign_lang'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/constants/mimeTypes.ts: -------------------------------------------------------------------------------- 1 | export const MIME_TYPES = { 2 | txt: 'text/plain', 3 | csv: 'text/csv', 4 | gz: 'application/gzip', 5 | json: 'application/json', 6 | jsonp: 'application/javascript', 7 | } as const; 8 | -------------------------------------------------------------------------------- /web/beacon-app/src/contexts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/contexts/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/contexts/__tests__/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/contexts/__tests__/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/features/apiKeys/api/apiKeysApiService.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | 6 | export function apiKeysRequest(request: Request): ApiAdapters['getApiKeys'] { 7 | return async (projectID: string) => { 8 | const response = (await request(`${APP_ROUTE.PROJECTS}/${projectID}/apikeys`, { 9 | method: 'GET', 10 | })) as any; 11 | 12 | return getValidApiResponse(response); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/apiKeys/api/deleteApiKeyApi.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 4 | import type { Request } from '@/application/api/ApiService'; 5 | import { getValidApiResponse } from '@/application/api/ApiService'; 6 | import { APP_ROUTE } from '@/constants'; 7 | 8 | export function deleteAPIKeyRequest(request: Request): ApiAdapters['deleteAPIKey'] { 9 | return async (apiKey: string) => { 10 | const response = (await request(`${APP_ROUTE.APIKEYS}/${apiKey}`, { 11 | method: 'DELETE', 12 | })) as unknown as AxiosResponse; 13 | 14 | return getValidApiResponse(response); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/apiKeys/components/GenerateAPIKeyForm.tsx: -------------------------------------------------------------------------------- 1 | //TODO: Implement GenerateAPIKeyForm 2 | export {}; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/apiKeys/schemas/generateAPIKeyValidationSchema.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | 3 | const generateAPIKeyValidationSchema = Yup.object().shape({ 4 | name: Yup.string().required('The key name is required.'), 5 | }); 6 | 7 | export default generateAPIKeyValidationSchema; 8 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/apiKeys/types/apiKeyService.ts: -------------------------------------------------------------------------------- 1 | import { APIKEY_STATUS } from '@/constants/rolesAndStatus'; 2 | 3 | export interface APIKey { 4 | id: string; 5 | client_id: string; 6 | client_secret: string; 7 | name: string; 8 | owner: string; 9 | permissions: string[]; 10 | created?: string; 11 | modified?: string; 12 | } 13 | 14 | export interface APIKeysQuery { 15 | getApiKeys: () => void; 16 | apiKeys: any; 17 | hasApiKeysFailed: boolean; 18 | wasApiKeysFetched: boolean; 19 | isFetchingApiKeys: boolean; 20 | error: any; 21 | } 22 | 23 | export type NewAPIKey = Omit; 24 | 25 | export type APIKeyStatus = keyof typeof APIKEY_STATUS; 26 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/apiKeys/types/createApiKeyService.ts: -------------------------------------------------------------------------------- 1 | import { UseMutateFunction } from '@tanstack/react-query'; 2 | 3 | import { APIKey } from './apiKeyService'; 4 | 5 | export interface APIKeyMutation { 6 | createProjectNewKey: UseMutateFunction; 7 | reset(): void; 8 | key: APIKey; 9 | hasKeyFailed: boolean; 10 | wasKeyCreated: boolean; 11 | isCreatingKey: boolean; 12 | error: any; 13 | } 14 | 15 | export interface NewAPIKey { 16 | name: string; 17 | permissions: string[]; 18 | } 19 | 20 | export type APIKeyDTO = { 21 | projectID: string; 22 | } & NewAPIKey; 23 | 24 | export const isKeyCreated = (mutation: APIKeyMutation): mutation is Required => 25 | mutation.wasKeyCreated && mutation.key != undefined; 26 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/apiKeys/types/deleteApiKeyService.ts: -------------------------------------------------------------------------------- 1 | import { UseMutateFunction } from '@tanstack/react-query'; 2 | 3 | import { APIKey } from './apiKeyService'; 4 | 5 | export interface DeleteAPIKeyMutation { 6 | deleteApiKey: UseMutateFunction; 7 | reset(): void; 8 | key: APIKey; 9 | hasKeyDeletedFailed: boolean; 10 | wasKeyDeleted: boolean; 11 | isDeletingKey: boolean; 12 | error: any; 13 | } 14 | 15 | export type APIKeyDTO = { 16 | topicID: string; 17 | }; 18 | 19 | export const isKeyDeleted = ( 20 | mutation: DeleteAPIKeyMutation 21 | ): mutation is Required => 22 | mutation.wasKeyDeleted && mutation.key != undefined; 23 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/api/ForgotPasswordApiService.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | export function forgotPasswordRequest(request: Request): ApiAdapters['forgotPassword'] { 6 | return async (email) => { 7 | const response = (await request(`${APP_ROUTE.FORGOT_PASSWORD}`, { 8 | method: 'POST', 9 | data: JSON.stringify(email), 10 | })) as any; 11 | 12 | return getValidApiResponse(response); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/api/LoginApiService.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | 6 | import type { UserAuthResponse } from '../types/LoginService'; 7 | 8 | export function loginRequest(request: Request): ApiAdapters['authenticateUser'] { 9 | return async (user) => { 10 | const response = (await request(`${APP_ROUTE.LOGIN}`, { 11 | method: 'POST', 12 | data: JSON.stringify(user), 13 | })) as any; 14 | 15 | return getValidApiResponse(response); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/api/ResetPasswordApi.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 4 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 5 | import { APP_ROUTE } from '@/constants'; 6 | 7 | import { ResetPasswordDTO } from '../types/ResetPasswordService'; 8 | 9 | export function resetPasswordRequest(request: Request): ApiAdapters['resetPassword'] { 10 | return async (payload: ResetPasswordDTO) => { 11 | const response = (await request(`${APP_ROUTE.RESET_PASSWORD}`, { 12 | method: 'POST', 13 | data: JSON.stringify(payload), 14 | })) as unknown as AxiosResponse; 15 | 16 | return getValidApiResponse(response); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/api/verifyTokenApiService.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | export function checkVerifyTokenRequest(request: Request): ApiAdapters['checkToken'] { 6 | return async (token: string) => { 7 | const response = (await request(`${APP_ROUTE.VERIFY_TOKEN}`, { 8 | method: 'POST', 9 | data: { token }, 10 | })) as any; 11 | return getValidApiResponse(response); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/components/Login/LoginFormValidation.ts: -------------------------------------------------------------------------------- 1 | import yup from 'yup'; 2 | 3 | const LoginFormValidation = yup.object().shape({ 4 | email: yup.string().email().required(), 5 | password: yup.string().required(), 6 | }); 7 | 8 | export default LoginFormValidation; 9 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/components/Login/__tests__/LoginForm.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { vi } from 'vitest'; 3 | 4 | import { customRender } from '../../../../../utils/test-utils'; 5 | import LoginForm from '../LoginForm'; 6 | 7 | describe('LoginForm', () => { 8 | it('should submit the form', async () => { 9 | const handleSubmit = vi.fn(); 10 | customRender(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/components/Verify/Fail.tsx: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/components/Verify/Success.tsx: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LoginForm } from './Login/LoginForm'; 2 | export { default as RegistrationForm } from './Register/RegistrationForm'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/hooks/useForgotPasswordForm.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@lingui/macro'; 2 | import { useFormik } from 'formik'; 3 | import { object, string } from 'yup'; 4 | 5 | export const FORM_INITIAL_VALUES = { 6 | email: '', 7 | } as any; 8 | 9 | export const FORM_VALIDATION_SCHEMA = object({ 10 | email: string() 11 | .trim() 12 | .email(t`Please enter a valid email address.`) 13 | .required(t`Email is required.`), 14 | }); 15 | 16 | export const FORM_OPTIONS = (onSubmit: any) => ({ 17 | initialValues: FORM_INITIAL_VALUES, 18 | validationSchema: FORM_VALIDATION_SCHEMA, 19 | onSubmit, 20 | }); 21 | 22 | export const useForgotPasswordForm = (onSubmit: any) => useFormik(FORM_OPTIONS(onSubmit)); 23 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api/LoginApiService'; 2 | export * from './api/RegisterApiService'; 3 | export * from './hooks/useLogin'; 4 | export * from './hooks/useRegister'; 5 | export * from './routes'; 6 | export * from './types/CreateAccountService'; 7 | export * from './types/LoginService'; 8 | export * from './types/RegisterService'; 9 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/routes/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as SuccessfulAccountCreation } from '../components/Register/SuccessfullAccountCreation'; 2 | export { default as LoginPage } from './LoginPage'; 3 | export { default as RegistrationPage } from './RegistrationPage'; 4 | export { default as VerifyPage } from './VerifyPage'; 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/routes/stories/Registration.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import Registration from '../RegistrationPage'; 4 | 5 | export default { 6 | title: 'features/auth/routes/Registration', 7 | component: Registration, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = {}; 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/routes/stories/SuccessfullAccountCreation.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import SuccessfulAccountCreation from '../../components/Register/SuccessfullAccountCreation'; 4 | 5 | export default { 6 | title: 'features/auth/components/SuccessfulAccountCreation', 7 | component: SuccessfulAccountCreation, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ( 11 | 12 | ); 13 | 14 | export const Default = Template.bind({}); 15 | Default.args = {}; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/types/CreateAccountService.ts: -------------------------------------------------------------------------------- 1 | import { UseMutateFunction } from '@tanstack/react-query'; 2 | 3 | import { NewUserAccount, NewUserResponseData } from './RegisterService'; 4 | 5 | export interface RegistrationMutation { 6 | createNewAccount: UseMutateFunction; 7 | reset(): void; 8 | user: NewUserResponseData; 9 | hasAccountFailed: boolean; 10 | wasAccountCreated: boolean; 11 | isCreatingAccount: boolean; 12 | error: unknown; 13 | } 14 | 15 | export const isAccountCreated = ( 16 | mutation: RegistrationMutation 17 | ): mutation is Required => 18 | mutation.wasAccountCreated && mutation.user !== undefined; 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/types/ForgotPasswordService.ts: -------------------------------------------------------------------------------- 1 | import { UseMutateFunction } from '@tanstack/react-query'; 2 | 3 | export interface ForgotPasswordMutation { 4 | forgotPassword: UseMutateFunction; 5 | reset: () => void; 6 | hasForgotPasswordFailed: boolean; 7 | wasForgotPasswordSuccessful: boolean; 8 | isLoading: boolean; 9 | error: any; 10 | } 11 | 12 | export interface ForgotPasswordDTO { 13 | email: string; 14 | } 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/auth/types/ResetPasswordService.ts: -------------------------------------------------------------------------------- 1 | import { UseMutateFunction } from '@tanstack/react-query'; 2 | 3 | export interface ResetPasswordMutation { 4 | resetPassword: UseMutateFunction; 5 | reset: () => void; 6 | hasResetPasswordFailed: boolean; 7 | wasResetPasswordSuccessful: boolean; 8 | isLoading: boolean; 9 | error: any; 10 | } 11 | 12 | export interface ResetPasswordDTO { 13 | token: string; 14 | password: string; 15 | pwcheck: string; 16 | } 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/api/StatusApiService.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { StatusResponse } from '../types/StatusService'; 6 | 7 | export function statusRequest(request: Request): ApiAdapters['getStatus'] { 8 | return async () => { 9 | const response = (await request(`${APP_ROUTE.STATUS}`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/components/ProjectDetails.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import ProjectDetailsStep from './ProjectDetailsStep'; 4 | // interface ProjectDetailsStepProps { 5 | // tenantID: string; 6 | // } 7 | 8 | export default { 9 | title: 'features/misc/components/ProjectDetails', 10 | component: ProjectDetailsStep, 11 | } as Meta; 12 | 13 | const Template: Story = (args) => ; 14 | 15 | export const Default = Template.bind({}); 16 | Default.args = {}; 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/components/QuickStart.tsx: -------------------------------------------------------------------------------- 1 | import SetupNewProject from '../components/SetupNewProject'; 2 | // import Templates from './Templates'; 3 | export default function QuickStart() { 4 | return ( 5 |
6 | 7 | {/* */} 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/components/QuickViewCard.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | type QuickViewCardProps = { 4 | title: string; 5 | color: string; 6 | children: ReactNode; 7 | }; 8 | 9 | function QuickViewCard({ title, color, children }: QuickViewCardProps) { 10 | return ( 11 |
15 |
{title}
16 |

{children}

17 |
18 | ); 19 | } 20 | 21 | export default QuickViewCard; 22 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/components/stories/AccessDocumentation.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import AccessDocumentationStep from '../AccessDocumentationStep'; 4 | 5 | export default { 6 | title: 'features/misc/components/AccessDocumentation', 7 | component: AccessDocumentationStep, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ( 11 | 12 | ); 13 | 14 | export const Default = Template.bind({}); 15 | Default.args = {}; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/components/stories/RevokeApiKeyModal.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import RevokeApiKeyModal, { RevokeApiKeyModalProps } from '../RevokeApiKeyModal'; 4 | 5 | export default { 6 | title: 'beacon/RevokeApiKeyModal', 7 | } as Meta; 8 | 9 | const Template: Story = (args) => ; 10 | 11 | export const Default = Template.bind({}); 12 | Default.args = { 13 | open: true, 14 | }; 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Home } from './routes/Home'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/routes/Home.tsx: -------------------------------------------------------------------------------- 1 | import AppLayout from '@/components/layout/AppLayout'; 2 | import { useOrgStore } from '@/store'; 3 | import { setCookie } from '@/utils/cookies'; 4 | 5 | import QuickStart from '../components/QuickStart'; 6 | import StarterVideos from '../components/StarterVideos/StarterVideo'; 7 | import WelcomeAttention from '../components/WelcomeAttention'; 8 | 9 | export default function Home() { 10 | const store = useOrgStore((state) => state) as any; 11 | const isAuthenticated = store.isAuthenticated; 12 | setCookie('authenticatedUser', isAuthenticated as string); 13 | 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/types/StatusService.ts: -------------------------------------------------------------------------------- 1 | export interface StatusResponse { 2 | status: string; 3 | uptime: string; 4 | version: string; 5 | } 6 | 7 | export interface StatusQuery { 8 | getStatus(): void; 9 | status: any; 10 | hasStatusFailed: boolean; 11 | wasStatusFetched: boolean; 12 | isFetchingStatus: boolean; 13 | error: any; 14 | } 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/home/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/api/deleteMemberApi.ts: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | 3 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 4 | import type { Request } from '@/application/api/ApiService'; 5 | import { getValidApiResponse } from '@/application/api/ApiService'; 6 | 7 | export function deleteMemberRequest(request: Request): ApiAdapters['deleteMember'] { 8 | return async (memberId: string) => { 9 | invariant(memberId, 'member id is missing'); 10 | const response = (await request(`/members/${memberId}`, { 11 | method: 'DELETE', 12 | })) as any; 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/api/getProfileAPI.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { MembersResponse } from '../types/memberServices'; 6 | 7 | export function profileRequest(request: Request): ApiAdapters['getProfile'] { 8 | return async () => { 9 | const response = (await request(`${APP_ROUTE.PROFILE}`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/api/memberApiService.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { MembersResponse } from '../types/memberServices'; 6 | 7 | export function memberRequest(request: Request): ApiAdapters['getMemberDetail'] { 8 | return async (memberID: string) => { 9 | const response = (await request(`${APP_ROUTE.MEMBERS}/${memberID}`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/api/memberListAPI.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { MembersResponse } from '../types/memberServices'; 6 | 7 | export function memberRequest(request: Request): ApiAdapters['getMemberList'] { 8 | return async () => { 9 | const response = (await request(`${APP_ROUTE.MEMBERS_LIST}`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/components/CancelModal/CancelAcctModal.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import CancelAcctModal from './CancelAcctModal'; 4 | interface CancelAcctModalProps { 5 | close: () => void; 6 | isOpen: boolean; 7 | } 8 | export default { 9 | title: 'beacon/CancelAcctModal', 10 | } as Meta; 11 | 12 | const Template: Story = (args) => ; 13 | 14 | export const Default = Template.bind({}); 15 | Default.args = {}; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/components/CancelModal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CancelAcctModal } from './CancelAcctModal'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/components/MemberDetails.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import MemberDetails from './MemberDetails'; 4 | 5 | export default { 6 | title: 'members/MemberDetails', 7 | component: MemberDetails, 8 | } as Meta; 9 | 10 | const Template: Story = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = {}; 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/members/routes/MemberPage.tsx: -------------------------------------------------------------------------------- 1 | import AppLayout from '@/components/layout/AppLayout'; 2 | import OrganizationsTable from '@/features/organization/components/OrganizationTable'; 3 | 4 | import MemberDetails from '../components/MemberDetails'; 5 | 6 | export default function MemberPage() { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/misc/components/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/misc/components/onboarding/SuccessfulTenantCreationModal.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import SuccessfulTenantCreationModal, { 4 | SuccessfulTenantCreationModalProps, 5 | } from './SuccessfulTenantCreationModal'; 6 | 7 | export default { 8 | title: 'beacon/SuccessFullTenantModal', 9 | } as Meta; 10 | 11 | const Template: Story = (args) => ( 12 | 13 | ); 14 | 15 | export const Default = Template.bind({}); 16 | Default.args = { 17 | open: true, 18 | }; 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/misc/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/misc/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/features/misc/routes/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/components/stepper/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Stepper } from './Stepper'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/components/steps/index.ts: -------------------------------------------------------------------------------- 1 | export { default as UserNameStep } from './name'; 2 | export { default as OrganizationStep } from './organization'; 3 | export { default as UserPreferenceStep } from './preference'; 4 | export { default as StepCounter } from './StepCounter'; 5 | export { default as WorkspaceStep } from './workspace'; 6 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/components/steps/preference/DeveloperSegment.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { memo } from 'react'; 3 | const DeveloperSegment = () => { 4 | return ( 5 |
6 |

7 | What kind of work do you do? 8 |

9 |

10 | Select up to 3. This will help us personalize your experience. 11 |

12 |
13 | ); 14 | }; 15 | 16 | export default memo(DeveloperSegment); 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/components/steps/preference/profession/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { memo } from 'react'; 3 | const ProfessionSegmentHeader = () => { 4 | return ( 5 | <> 6 |

7 | What will you use Ensign for? 8 |

9 |

10 | This will help us plan new features and improvements. 11 |

12 | 13 | ); 14 | }; 15 | 16 | export default memo(ProfessionSegmentHeader); 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/components/steps/preference/profession/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProfessionSegment } from './ProfessionSegment'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/hooks/useNameForm.tsx: -------------------------------------------------------------------------------- 1 | import { t } from '@lingui/macro'; 2 | import { useFormik } from 'formik'; 3 | import { object, string } from 'yup'; 4 | 5 | export const FORM_INITIAL_VALUES = { 6 | name: '', 7 | } as any; 8 | 9 | export const FORM_VALIDATION_SCHEMA = object({ 10 | name: string() 11 | .trim() 12 | .required(t`Name is required.`), 13 | }); 14 | 15 | export const FORM_OPTIONS = (onSubmit: any, initialValues: any) => ({ 16 | initialValues: { 17 | ...FORM_INITIAL_VALUES, 18 | ...initialValues, 19 | }, 20 | validationSchema: FORM_VALIDATION_SCHEMA, 21 | onSubmit, 22 | }); 23 | 24 | export const useNameForm = (onSubmit: any, initialValues: any) => 25 | useFormik(FORM_OPTIONS(onSubmit, initialValues)); 26 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/index.ts: -------------------------------------------------------------------------------- 1 | export { default as OnBoardingPage } from './routes/OnboardingPage'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@rotational/beacon-core'; 2 | import React from 'react'; 3 | 4 | type Props = { 5 | children: React.ReactNode; 6 | }; 7 | 8 | const OnboardingFormLayout = ({ children }: Props) => { 9 | return ( 10 | <> 11 | 12 |
{children}
13 |
14 | 15 | ); 16 | }; 17 | 18 | export default OnboardingFormLayout; 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/onboarding/shared/constants.ts: -------------------------------------------------------------------------------- 1 | export enum ONBOARDING_STEPS { 2 | ORGANIZATION = 1, 3 | WORKSPACE = 2, 4 | NAME = 3, 5 | PREFERENCE = 4, 6 | COMPLETE = 5, 7 | } 8 | 9 | export enum ONBOARDING_STATUS { 10 | ONBOARDING = 'Onboarding', 11 | ACTIVE = 'Active', 12 | } 13 | 14 | export const WORKSPACE_DOMAIN_BASE = 'https://rotational.app/'; 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/api/orgDetailApi.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { OrgResponse } from '../types/organizationService'; 6 | 7 | export function orgRequest(request: Request): ApiAdapters['orgDetail'] { 8 | return async (orgID: string) => { 9 | const response = (await request(`${APP_ROUTE.ORG_DETAIL}/${orgID}`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/api/organizationListApi.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { OrgListResponse } from '../types/organizationService'; 6 | 7 | export function organizationRequest(request: Request): ApiAdapters['getOrganizationList'] { 8 | return async () => { 9 | const response = (await request(`${APP_ROUTE.ORGANIZATION}`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/api/switchOrganizationApi.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | import type { UserAuthResponse } from '@/features/auth/types/LoginService'; 6 | 7 | export function switchOrganizationRequest(request: Request): ApiAdapters['switchOrganization'] { 8 | return async (orgId: string) => { 9 | const response = (await request(`${APP_ROUTE.SWITCH}`, { 10 | method: 'POST', 11 | data: { 12 | org_id: orgId, 13 | }, 14 | })) as any; 15 | 16 | return getValidApiResponse(response); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/components/DeleteOrgModal/DeleteOrgModal.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/react'; 2 | 3 | import DeleteOrgModal from './DeleteOrgModal'; 4 | export default { 5 | title: 'organizations/DeleteOrgModal', 6 | } as Meta; 7 | 8 | interface DeleteOrgModalProps { 9 | close: () => void; 10 | isOpen: boolean; 11 | } 12 | 13 | const Template: Story = (args) => ; 14 | 15 | export const Default = Template.bind({}); 16 | Default.args = { 17 | close: () => {}, 18 | isOpen: true, 19 | }; 20 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/components/DeleteOrgModal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DeleteOrgModal } from './DeleteOrgModal'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as OrganizationPage } from './routes/OrganizationPage'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/routes/OrganizationPage.tsx: -------------------------------------------------------------------------------- 1 | import { Heading } from '@rotational/beacon-core'; 2 | 3 | import AppLayout from '@/components/layout/AppLayout'; 4 | import TenantTable from '@/features/tenants/components/TenantTable'; 5 | 6 | import OrganizationDetails from '../components/OrganizationDetails'; 7 | 8 | export default function OrganizationPage() { 9 | return ( 10 | 11 | 12 | Organization Details 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/types/organizationService.ts: -------------------------------------------------------------------------------- 1 | export interface OrgResponse { 2 | id: string; 3 | name: string; 4 | domain: string; 5 | created: string; 6 | modified: string; 7 | } 8 | 9 | export interface OrgDetailQuery { 10 | getOrgDetail(): void; 11 | org: any; 12 | hasOrgFailed: boolean; 13 | wasOrgFetched: boolean; 14 | isFetchingOrg: boolean; 15 | error: any; 16 | } 17 | 18 | export interface OrgListQuery { 19 | getOrgList(): void; 20 | organizations: any; 21 | hasOrgListFailed: boolean; 22 | wasOrgListFetched: boolean; 23 | isFetchingOrgList: boolean; 24 | error: any; 25 | } 26 | 27 | export interface OrgListResponse { 28 | organizations: OrgResponse[]; 29 | } 30 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/types/switchService.ts: -------------------------------------------------------------------------------- 1 | import { UserAuthResponse } from '../../auth/types/LoginService'; 2 | 3 | export interface SwitchMutation { 4 | switch: (orgId: string) => void; 5 | hasSwitchFailed: boolean; 6 | wasSwitchFetched: boolean; 7 | isSwitching: boolean; 8 | auth: UserAuthResponse; 9 | error: any; 10 | reset: () => void; 11 | } 12 | 13 | export interface SwitchDTO { 14 | org_id: string; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/organization/utils.tsx: -------------------------------------------------------------------------------- 1 | // import { Project } from '../types'; 2 | import { formatDate } from '@/utils/formatDate'; 3 | export const getOrgData = (org: any | undefined) => { 4 | if (org && org !== null) { 5 | const { id, name, created, owner } = org; 6 | return [ 7 | { 8 | label: 'Name', 9 | value: name, 10 | }, 11 | { 12 | label: 'Organization ID', 13 | value: id, 14 | }, 15 | { 16 | label: 'Owner', 17 | value: owner, 18 | }, 19 | { 20 | label: 'Date Created', 21 | value: formatDate(new Date(created)), 22 | }, 23 | ]; 24 | } 25 | return []; 26 | }; 27 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/api/projectListAPI.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { ProjectsResponse } from '../types/projectService'; 6 | 7 | export function projectsRequest(request: Request): ApiAdapters['getProjectList'] { 8 | return async (tenantID: string) => { 9 | const response = (await request(`${APP_ROUTE.TENANTS}/${tenantID}/projects`, { 10 | method: 'GET', 11 | })) as any; 12 | return getValidApiResponse(response); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/api/projectQueryApiService.tsx: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { ProjectQueryDTO, ProjectQueryResponse } from '../types/projectQueryService'; 6 | export function projectQueryAPI(request: Request): ApiAdapters['projectQuery'] { 7 | return async ({ projectID, query }: ProjectQueryDTO) => { 8 | const response = (await request(`${APP_ROUTE.PROJECTS}/${projectID}/query`, { 9 | method: 'POST', 10 | data: JSON.stringify({ 11 | query, 12 | }), 13 | })) as unknown as any; 14 | 15 | return getValidApiResponse(response); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/api/projectStatsApiService.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | import { ProjectQuickViewResponse } from '../types/projectService'; 6 | 7 | function projectStatsApiRequest(request: Request): ApiAdapters['getProjectStats'] { 8 | return async (tenantID: string) => { 9 | //console.log('[] tenantId', tenantID); 10 | const response = (await request(`${APP_ROUTE.TENANTS}/${tenantID}/projects/stats`, { 11 | method: 'GET', 12 | })) as any; 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | 17 | export default projectStatsApiRequest; 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/components/ChangeOwner/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as ChangeOwnerModal } from './ChangeOwnerModal'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/components/EditProject/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as EditProjectModal } from './EditProjectModal'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/components/__tests__/__snapshots__/ProjectsTable.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`ProjectsTable > should render table with correct columns 1`] = ` 4 |
5 |
8 |
11 |

12 | Sorry we are having trouble listing your projects, please refresh the page and try again. 13 |

14 |
15 |
16 |
17 | `; 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/hooks/useProjectActive.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const useProjectActive = (projectID: string) => { 4 | const projectKEY = 'isActiveProject-' + projectID; 5 | const getIsProjectActive = localStorage.getItem(projectKEY); 6 | const [isActive, setIsActive] = useState(getIsProjectActive === 'true'); 7 | 8 | return { 9 | isActive, 10 | setIsActive, 11 | }; 12 | }; 13 | 14 | export default useProjectActive; 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProjectDetailPage } from './routes/ProjectDetailPage'; 2 | export { default as ProjectsPage } from './routes/ProjectsPage'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/types/Project.ts: -------------------------------------------------------------------------------- 1 | import { MemberResponse } from '@/features/members/types/memberServices'; 2 | import { QuickViewData } from '@/hooks/useFetchQuickView/quickViewService'; 3 | export type Project = { 4 | created: string; 5 | id: string; 6 | modified: string; 7 | name: string; 8 | tenant_id: string; 9 | description?: string; 10 | status?: string; 11 | owner: Partial; 12 | active_topics?: number; 13 | data_storage?: QuickViewData; 14 | }; 15 | 16 | export enum ProjectStatus { 17 | ACTIVE = 'ACTIVE', 18 | INACTIVE = 'INACTIVE', 19 | DELETED = 'DELETED', 20 | INCOMPLETE = 'Incomplete', 21 | COMPLETE = 'Complete', 22 | } 23 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/types/createTopicService.ts: -------------------------------------------------------------------------------- 1 | import { UseMutateFunction } from '@tanstack/react-query'; 2 | 3 | import { Topic } from '@/features/topics/types/topicService'; 4 | 5 | export interface TopicMutation { 6 | createTopic: UseMutateFunction; 7 | reset(): void; 8 | topic: any; 9 | hasTopicFailed: boolean; 10 | wasTopicCreated: boolean; 11 | isCreatingTopic: boolean; 12 | error: any; 13 | } 14 | 15 | export interface NewTopic { 16 | topic_name: string; 17 | } 18 | 19 | export type NewTopicDTO = { 20 | projectID: string; 21 | } & NewTopic; 22 | 23 | export const isTopicCreated = (mutation: TopicMutation): mutation is Required => 24 | mutation.wasTopicCreated && mutation.topic != undefined; 25 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/projects/types/generateAPIKeyService.ts: -------------------------------------------------------------------------------- 1 | export interface NewApiKey { 2 | name: string; 3 | permissions: string[]; 4 | } 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/sandbox/types/accountType.ts: -------------------------------------------------------------------------------- 1 | export const ACCOUNT_TYPE = { 2 | SANDBOX: 'sandbox', 3 | }; 4 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/sandbox/util/utils.ts: -------------------------------------------------------------------------------- 1 | import { ACCOUNT_TYPE } from '../types/accountType'; 2 | 3 | export const isSandboxAccount = (account: string) => { 4 | return account === ACCOUNT_TYPE.SANDBOX; 5 | }; 6 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/api/getInviteTeamMemberRequest.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | 5 | export function getInviteTeamMemberRequest(request: Request): ApiAdapters['getInviteTeamMember'] { 6 | return async (token: string) => { 7 | const response = (await request(`${APP_ROUTE.INVITE}/${token}`)) as any; 8 | 9 | return getValidApiResponse(response); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/api/invitationAuthenticationRequest.ts: -------------------------------------------------------------------------------- 1 | import { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import { getValidApiResponse, Request } from '@/application/api/ApiService'; 3 | import { APP_ROUTE } from '@/constants'; 4 | import { UserAuthResponse } from '@/features/auth'; 5 | 6 | export function invitationAuthenticationRequest( 7 | request: Request 8 | ): ApiAdapters['getInvitationAuthentication'] { 9 | return async (token: string) => { 10 | const response = (await request(`${APP_ROUTE.INVITE}/accept`, { 11 | method: 'POST', 12 | data: { 13 | token, 14 | }, 15 | })) as any; 16 | 17 | return getValidApiResponse(response); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/components/InviteTeamMemberVerification.tsx: -------------------------------------------------------------------------------- 1 | import { useLoaderData } from 'react-router-dom'; 2 | 3 | import ExistingUserInvitationPage from './ExistingUserInvitationPage'; 4 | import NewUserInvitationPage from './NewUserInvitationPage'; 5 | 6 | export const InviteTeamMemberVerification = () => { 7 | const loaderData = useLoaderData() as any; 8 | 9 | if (loaderData && loaderData.has_account) { 10 | return ; 11 | } 12 | return ; 13 | }; 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/components/RegisterNewUser/schemas/newInviteRegistrationFormValidation.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@lingui/macro'; 2 | import * as Yup from 'yup'; 3 | 4 | import { NewInvitedUserAccount } from '@/features/auth'; 5 | 6 | const validationSchema = Yup.object().shape({ 7 | email: Yup.string() 8 | .email() 9 | .required(t`The email address is required.`), 10 | password: Yup.string().required(t`The password is required.`), 11 | pwcheck: Yup.string() 12 | .oneOf([Yup.ref('password'), null], t`The passwords must match.`) 13 | .required(t`Please re-enter your password to confirm.`), 14 | invite_token: Yup.string().notRequired(), 15 | }) satisfies Yup.SchemaOf; 16 | 17 | export default validationSchema; 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/constants/query-key.ts: -------------------------------------------------------------------------------- 1 | export const QUERY_KEY = { 2 | MEMBERS_LIST: 'memberList', 3 | INVITE_MEMBER: 'inviteMember', 4 | } as const; 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/InviteTeamMemberVerification'; 2 | export * from './routes/TeamsPage'; 3 | 4 | // loaders 5 | export * from './loaders/inviteTeamMember'; 6 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/loaders/inviteTeamMember.ts: -------------------------------------------------------------------------------- 1 | import { LoaderFunctionArgs } from 'react-router-dom'; 2 | 3 | import axiosInstance from '@/application/api/ApiService'; 4 | 5 | import { getInviteTeamMemberRequest } from '../api/getInviteTeamMemberRequest'; 6 | 7 | export const inviteTeamMemberLoader = async ({ request }: LoaderFunctionArgs) => { 8 | const url = new URL(request.url); 9 | const token = url.searchParams.get('token') || ''; 10 | 11 | const response = await getInviteTeamMemberRequest(axiosInstance)(token); 12 | return response; 13 | }; 14 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/types/changeRoleFormDto.ts: -------------------------------------------------------------------------------- 1 | export type ChangeRoleFormDto = { 2 | name: string; 3 | current_role: string; 4 | role: string; 5 | }; 6 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/types/invites.ts: -------------------------------------------------------------------------------- 1 | import { UseMutateFunction } from '@tanstack/react-query'; 2 | 3 | import { UserAuthResponse } from '@/features/auth'; 4 | 5 | export interface InviteAuthenticationMutation { 6 | invite: UseMutateFunction; 7 | reset(): void; 8 | auth: UserAuthResponse; 9 | hasInviteAuthenticationFailed: boolean; 10 | wasAuthenticated: boolean; 11 | isFetchingInviteAuthentication: boolean; 12 | error: any; 13 | } 14 | 15 | export interface InviteAuthenticationDTO { 16 | token: string; 17 | } 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/teams/types/member.ts: -------------------------------------------------------------------------------- 1 | import { MEMBER_ROLE, MEMBER_STATUS } from '@/constants/rolesAndStatus'; 2 | 3 | export type Member = { 4 | id: string; 5 | email: string; 6 | name: string; 7 | role: MemberRole; 8 | status: MemberStatus; 9 | created: string; 10 | modified: string; 11 | }; 12 | 13 | export type MemberRole = keyof typeof MEMBER_ROLE; 14 | export type MemberStatus = keyof typeof MEMBER_STATUS; 15 | 16 | export enum MemberStatusEnum { 17 | PENDING = 'Pending', 18 | CONFIRMED = 'Confirmed', 19 | ACTIVE = 'Active', 20 | ONBOARDING = 'Onboarding', 21 | } 22 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/tenants/api/createTenantApiService.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | 6 | export function createTenantRequest(request: Request): ApiAdapters['createTenant'] { 7 | return async () => { 8 | const response = (await request(`${APP_ROUTE.TENANTS}`, { 9 | method: 'POST', 10 | })) as any; 11 | 12 | return getValidApiResponse(response); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/tenants/api/tenantListAPI.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | 6 | import type { UserTenantResponse } from '../types/tenantServices'; 7 | 8 | export function tenantsRequest(request: Request): ApiAdapters['getTenantList'] { 9 | return async () => { 10 | const response = (await request(`${APP_ROUTE.TENANTS}`, { 11 | method: 'GET', 12 | })) as any; 13 | 14 | return getValidApiResponse(response); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/tenants/hooks/useCreateTenant.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@tanstack/react-query'; 2 | 3 | import axiosInstance from '@/application/api/ApiService'; 4 | 5 | import { createTenantRequest } from '../api/createTenantApiService'; 6 | import { TenantMutation } from '../types/createTenantService'; 7 | 8 | export function useCreateTenant(): TenantMutation { 9 | const mutation = useMutation(createTenantRequest(axiosInstance), { 10 | retry: 0, 11 | }); 12 | 13 | return { 14 | createTenant: mutation.mutate, 15 | tenant: mutation.data, 16 | hasTenantFailed: mutation.isError, 17 | isFetchingTenant: mutation.isLoading, 18 | wasTenantFetched: mutation.isSuccess, 19 | error: mutation.error, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/tenants/types/createTenantService.ts: -------------------------------------------------------------------------------- 1 | export interface TenantMutation { 2 | createTenant(): void; 3 | tenant: any; 4 | hasTenantFailed: boolean; 5 | wasTenantFetched: boolean; 6 | isFetchingTenant: boolean; 7 | error: any; 8 | } 9 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/tenants/types/tenantServices.ts: -------------------------------------------------------------------------------- 1 | export interface UserTenantResponse { 2 | tenants: ITenant[]; 3 | prev_page_token: string; 4 | next_page_token: string; 5 | } 6 | 7 | export interface ITenant { 8 | id: string; 9 | name: string; 10 | environment_type: string; 11 | created: string; 12 | modified: string; 13 | } 14 | 15 | export interface TenantsQuery { 16 | getTenants(): void; 17 | tenants: any; 18 | hasTenantsFailed: boolean; 19 | wasTenantsFetched: boolean; 20 | isFetchingTenants: boolean; 21 | error: any; 22 | } 23 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/tenants/utils/tenant.ts: -------------------------------------------------------------------------------- 1 | import { ITenant } from '../types/tenantServices'; 2 | 3 | export const getRecentTenant = (tenants: ITenant[]) => { 4 | // for now, just return the first tenant 5 | if (tenants && tenants.length > 0) { 6 | const recent = tenants[0]; 7 | const { name, id } = recent; 8 | return [ 9 | { 10 | label: 'Tenant Name', 11 | value: name, 12 | }, 13 | { 14 | label: 'Tenant ID', 15 | value: id, 16 | }, 17 | ]; 18 | } 19 | return []; 20 | }; 21 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/api/topicDetailApiService.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | 6 | import { Topic } from '../types/topicService'; 7 | export function topicRequest(request: Request): ApiAdapters['getTopic'] { 8 | return async (topicID: string) => { 9 | const response = (await request(`${APP_ROUTE.TOPICS}/${topicID}`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/api/topicEventsApiService.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | 6 | import { Topic } from '../types/topicService'; 7 | export function topicEventsRequest(request: Request): ApiAdapters['getTopicEvents'] { 8 | return async (topicID: string) => { 9 | const response = (await request(`${APP_ROUTE.TOPICS}/${topicID}/events`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/api/topicsApiService.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiResponse } from '@/application/api/ApiService'; 4 | import { APP_ROUTE } from '@/constants'; 5 | 6 | import { TopicsResponse } from '../types/topicService'; 7 | export function topicsRequest(request: Request): ApiAdapters['getTopics'] { 8 | return async (projectID: string) => { 9 | const response = (await request(`${APP_ROUTE.PROJECTS}/${projectID}/topics`, { 10 | method: 'GET', 11 | })) as any; 12 | 13 | return getValidApiResponse(response); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/components/TopicQueryResult/MimeTypeResult/HTMLResult.tsx: -------------------------------------------------------------------------------- 1 | import pretty from 'pretty'; 2 | import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; 3 | import { irBlack } from 'react-syntax-highlighter/dist/esm/styles/hljs'; 4 | 5 | type HTMLResultProps = { 6 | data: any; 7 | }; 8 | 9 | const HTMLResult = ({ data }: HTMLResultProps) => { 10 | const formattedHtml = pretty(data); 11 | return ( 12 | 13 | {formattedHtml} 14 | 15 | ); 16 | }; 17 | 18 | export default HTMLResult; 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/components/TopicQueryResult/MimeTypeResult/JSONResult.tsx: -------------------------------------------------------------------------------- 1 | import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; 2 | import { tomorrowNightBright } from 'react-syntax-highlighter/dist/esm/styles/hljs'; 3 | 4 | type JSONResultProps = { 5 | data: any; 6 | }; 7 | 8 | const JSONResult = ({ data }: JSONResultProps) => { 9 | return ( 10 | 11 | {data} 12 | 13 | ); 14 | }; 15 | 16 | export default JSONResult; 17 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/components/TopicQueryResult/MimeTypeResult/XMLResult.tsx: -------------------------------------------------------------------------------- 1 | // This component is responsible for rendering the XML result of a query. 2 | import React from 'react'; 3 | import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; 4 | import { tomorrowNightBright } from 'react-syntax-highlighter/dist/esm/styles/hljs'; 5 | import xmlFormatter from 'xml-formatter'; 6 | 7 | type XMLResultProps = { 8 | data: any; 9 | }; 10 | 11 | const XMLResult: React.FC = ({ data }) => { 12 | const formattedData = xmlFormatter(data); 13 | 14 | return ( 15 | 16 | {formattedData} 17 | 18 | ); 19 | }; 20 | 21 | export default XMLResult; 22 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/components/TopicQueryResult/MimeTypeResult/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BinaryResult } from './BinaryResult'; 2 | export { default as XMLResult } from './XMLResult'; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/components/TopicStateTag.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | 3 | import { Tag } from '@/components/ui/Tag'; 4 | import { TOPIC_STATE } from '@/constants/rolesAndStatus'; 5 | 6 | interface TopicStateTagProps { 7 | status: string; 8 | } 9 | 10 | const topicStateMap = { 11 | [TOPIC_STATE.ACTIVE]: 'success', 12 | [TOPIC_STATE.ARCHIVED]: 'warning', 13 | [TOPIC_STATE.DELETING]: 'error', 14 | } as const; 15 | 16 | const TopicStateTag = ({ status }: TopicStateTagProps) => { 17 | return ( 18 | 19 | 20 | {status} 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default TopicStateTag; 27 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/hooks/useTopicQueryInputForm.ts: -------------------------------------------------------------------------------- 1 | import { useFormik } from 'formik'; 2 | 3 | import { ProjectQueryDTO } from '@/features/projects/types/projectQueryService'; 4 | import { QUERY_INPUT_FORM_OPTIONS } from '@/features/topics/schemas/topicQueryInputValidationSchema'; 5 | export const useTopicQueryInputForm = ( 6 | onSubmit: any, 7 | initialValues: Omit 8 | ) => useFormik(QUERY_INPUT_FORM_OPTIONS(onSubmit, initialValues)); 9 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as TopicDetailPage } from './routes/TopicDetailPage'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/features/topics/types/topicEventsService.ts: -------------------------------------------------------------------------------- 1 | export interface TopicEvents { 2 | type: string; 3 | version: string; 4 | mimetype: string; 5 | events: { 6 | value: number; 7 | percent: number; 8 | }; 9 | storage: { 10 | value: number; 11 | units: string; 12 | percent: number; 13 | }; 14 | } 15 | 16 | export interface TopicEventsQuery { 17 | getTopicEvents: () => void; 18 | hasTopicEventsFailed: boolean; 19 | isFetchingTopicEvents: boolean; 20 | topicEvents: TopicEvents[]; 21 | wasTopicEventsFetched: boolean; 22 | error: any; 23 | } 24 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/__tests__/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/hooks/__tests__/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useAccountType.ts: -------------------------------------------------------------------------------- 1 | import { useOrgStore } from '@/store'; 2 | 3 | function useAccountType() { 4 | const state = useOrgStore((state: any) => state) as any; 5 | const accountType = state.account as string; 6 | 7 | return accountType; 8 | } 9 | 10 | export default useAccountType; 11 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | // useAuth hook base on react-query useLogin hook and add some logic to handle auth token 2 | import { queryClient } from '@/application/config/react-query'; 3 | import { useOrgStore } from '@/store'; 4 | import { clearCookies } from '@/utils/cookies'; 5 | export const useAuth = () => { 6 | const org = useOrgStore.getState() as any; 7 | 8 | const isAuthenticated = !!org.isAuthenticated; 9 | 10 | function logout() { 11 | org.reset(); 12 | clearCookies(); 13 | queryClient.clear(); 14 | } 15 | 16 | return { 17 | isAuthenticated, 18 | logout, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useDrawer.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | type State = { 4 | isOpen: boolean; 5 | openDrawer: () => void; 6 | closeDrawer: () => void; 7 | }; 8 | 9 | const useDrawer = create((set) => ({ 10 | isOpen: false, 11 | openDrawer: () => set((state) => ({ ...state, isOpen: true })), 12 | closeDrawer: () => set((state) => ({ ...state, isOpen: false })), 13 | })); 14 | 15 | export default useDrawer; 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useFetchPermissions/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useFetchPermissions } from './useFetchPermissions'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useFetchPermissions/permissionsApiService.ts: -------------------------------------------------------------------------------- 1 | import type { ApiAdapters } from '@/application/api/ApiAdapters'; 2 | import type { Request } from '@/application/api/ApiService'; 3 | import { getValidApiError, getValidApiResponse } from '@/application/api/ApiService'; 4 | 5 | const permissionsRequest = 6 | (request: Request): ApiAdapters['getPermissions'] => 7 | async () => { 8 | const link = `/apikeys/permissions`; 9 | try { 10 | const response = (await request(`${link}`, { 11 | method: 'GET', 12 | })) as any; 13 | 14 | return getValidApiResponse(response); 15 | } catch (e: any) { 16 | getValidApiError(e); 17 | } 18 | }; 19 | 20 | export default permissionsRequest; 21 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useFetchQuickView/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useFetchTenantQuickView } from './useFetchQuickView'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useFetchQuickView/quickViewService.ts: -------------------------------------------------------------------------------- 1 | export type QuickViewKey = 'project' | 'tenant'; 2 | 3 | export interface QuickViewData { 4 | name: string; 5 | value: number; 6 | units?: string; 7 | percent?: number; 8 | } 9 | export interface QuickViewDTO { 10 | id: string; 11 | key: QuickViewKey; 12 | } 13 | 14 | export interface QuickViewResponse { 15 | data: QuickViewData[]; 16 | } 17 | 18 | export interface QuickViewQuery { 19 | getQuickView: () => void; 20 | hasQuickViewFailed: boolean; 21 | isFetchingQuickView: boolean; 22 | quickView: any; 23 | wasQuickViewFetched: boolean; 24 | error: any; 25 | } 26 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useFocus.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | type UseFocus = [ 4 | boolean, 5 | { 6 | onFocus: () => void; 7 | onBlur: () => void; 8 | } 9 | ]; 10 | 11 | export default function useFocus(): UseFocus { 12 | const [focused, setFocused] = useState(false); 13 | 14 | const onFocus = () => setFocused(true); 15 | const onBlur = () => setFocused(false); 16 | 17 | return [focused, { onFocus, onBlur }]; 18 | } 19 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useGetCurrentTenant.tsx: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/hooks/useQueryParams.tsx: -------------------------------------------------------------------------------- 1 | import queryString from 'query-string'; 2 | import { useLocation } from 'react-router-dom'; 3 | 4 | const useQueryParams = () => { 5 | const location = useLocation(); 6 | const params = queryString.parse(location.search); // Parse the query string 7 | 8 | return params; 9 | }; 10 | 11 | export default useQueryParams; 12 | -------------------------------------------------------------------------------- /web/beacon-app/src/lib/__tests__/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/lib/__tests__/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/lib/createGenericContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | interface Props { 3 | value: any; 4 | children: React.ReactNode; 5 | } 6 | 7 | //TODO: improve this later to be more generic 8 | export const createGenericContext = (defaultValue: any) => { 9 | const context = React.createContext(defaultValue); 10 | const Provider = ({ value, children }: Props) => { 11 | return React.createElement(context.Provider, { value }, children); 12 | }; 13 | return { context, Provider }; 14 | }; 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import { createGenericContext } from './createGenericContext'; 2 | 3 | export { createGenericContext }; 4 | -------------------------------------------------------------------------------- /web/beacon-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom/client'; 5 | 6 | import App from './App'; 7 | import { appConfig } from './application/config'; 8 | import initSentry from './application/config/sentry'; 9 | 10 | console.info('initializing beacon ui', appConfig.nodeENV, appConfig.version, appConfig.revision); 11 | initSentry(); 12 | 13 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /web/beacon-app/src/providers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/providers/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/providers/__tests__/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/providers/__tests__/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/providers/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement, ReactNode } from 'react'; 2 | 3 | type AppProvidersProps = { 4 | children: ReactNode; 5 | }; 6 | 7 | function AppProviders({ children }: AppProvidersProps): ReactElement { 8 | return
{children}
; 9 | } 10 | 11 | export default AppProviders; 12 | -------------------------------------------------------------------------------- /web/beacon-app/src/store/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/beacon-app/src/store/.gitkeep -------------------------------------------------------------------------------- /web/beacon-app/src/store/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useOrgStore } from './useOrgStore'; 2 | -------------------------------------------------------------------------------- /web/beacon-app/src/types/MenuItem.ts: -------------------------------------------------------------------------------- 1 | export type MenuItem = { 2 | name: string; 3 | icon: JSX.Element; 4 | href: string; 5 | href_linked?: string; 6 | isExternal?: boolean; 7 | isMail?: boolean; 8 | dropdownItems?: Pick[]; 9 | }; 10 | -------------------------------------------------------------------------------- /web/beacon-app/src/types/app.d.ts: -------------------------------------------------------------------------------- 1 | interface IStats { 2 | name: string; 3 | value: number; 4 | units?: string; 5 | percent?: number; 6 | } 7 | -------------------------------------------------------------------------------- /web/beacon-app/src/types/cypress-browserify-preprocessor.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@cypress/browserify-preprocessor' { 2 | type Plugin = [string, Record]; 3 | 4 | interface Browserify { 5 | defaultOptions: { 6 | browserifyOptions: { 7 | plugin: Plugin[]; 8 | }; 9 | }; 10 | } 11 | 12 | const browserify: Browserify; 13 | 14 | export default browserify; 15 | } 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/types/navigator.d.ts: -------------------------------------------------------------------------------- 1 | interface Navigator { 2 | msSaveBlob: (blob: any, defaultName?: string) => boolean; 3 | } 4 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/decodeToken.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prettier/prettier */ 2 | 3 | import jwt from 'jwt-decode'; 4 | 5 | export const decodeToken = (token: string) => { 6 | return jwt(token); 7 | }; 8 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/error-message.ts: -------------------------------------------------------------------------------- 1 | //TODO: Add more error messages here and improve this class to be more robust 2 | 3 | export default class ErrorMessage { 4 | static readonly somethingWentWrong = 5 | 'Something went wrong. Please contact us at support@rotational.io for assistance.'; 6 | static readonly NETWORK_ERROR = 7 | 'No internet connection. Please check your internet connection and try again.'; 8 | } 9 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/formatData.ts: -------------------------------------------------------------------------------- 1 | import { UserTenantResponse } from '@/features/tenants/types/tenantServices'; 2 | export const getRecentTenant = (tenants: UserTenantResponse) => { 3 | if (!tenants || tenants.tenants.length === 0) { 4 | return null; 5 | } 6 | // get the recent tenant by modified date 7 | const recentTenant = tenants?.tenants.reduce((prev: any, current: any) => { 8 | return prev.modified > current.modified ? prev : current; 9 | }); 10 | return recentTenant; 11 | }; 12 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/getInitials.ts: -------------------------------------------------------------------------------- 1 | export const getInitials = (name: string) => { 2 | const nameArray = name?.split(' ') || ['']; 3 | const initials = 4 | nameArray.length >= 2 5 | ? nameArray[0].charAt(0) + nameArray[1].charAt(0) 6 | : nameArray[0].charAt(0) + nameArray[0].charAt(1); 7 | 8 | return initials.toUpperCase(); 9 | }; 10 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/inputSanitzer.ts: -------------------------------------------------------------------------------- 1 | import DOMPurify from 'dompurify'; 2 | 3 | export const inputSanitizer = (input: string) => { 4 | // prevent XSS attacks 5 | const sanitizedInput = DOMPurify.sanitize(input); 6 | // prevent SQL injection 7 | const sanitizedSqlInjection = sanitizedInput.replace(/'/g, "\\'"); 8 | // prevent JS injection 9 | const jsInjectionSafeInput = sanitizedSqlInjection.replace(//g, '>'); 10 | // prevent leading and trailing spaces 11 | const finalSanitizedInput = jsInjectionSafeInput.trim(); 12 | 13 | return finalSanitizedInput; 14 | }; 15 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/lazy-import.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // named imports for React.lazy: https://github.com/facebook/react/issues/14603#issuecomment-726551598 4 | export function lazyImport< 5 | T extends React.ComponentType, 6 | I extends { [K2 in K]: T }, 7 | K extends keyof I 8 | >(factory: () => Promise, name: K): I { 9 | return Object.create({ 10 | [name]: React.lazy(() => factory().then((module) => ({ default: module[name] }))), 11 | }); 12 | } 13 | 14 | // Usage 15 | // const { Home } = lazyImport(() => import("./Home"), "Home"); 16 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | // isolote the logger so we can mock it in tests 2 | export {}; 3 | -------------------------------------------------------------------------------- /web/beacon-app/src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | // capitalize a string 2 | export const capitalize = (str: string) => { 3 | return str.charAt(0).toUpperCase() + str.slice(1); 4 | }; 5 | -------------------------------------------------------------------------------- /web/beacon-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly REACT_APP_QUARTERDECK_BASE_URL: string; 5 | readonly REACT_APP_TENANT_BASE_URL: string; 6 | 7 | readonly REACT_APP_SENTRY_DSN: string; 8 | readonly REACT_APP_SENTRY_ENVIRONMENT: string; 9 | readonly REACT_APP_SENTRY_EVENT_ID: string; 10 | 11 | readonly REACT_APP_ANALYTICS_ID: string; 12 | 13 | readonly REACT_APP_VERSION_NUMBER: string; 14 | readonly REACT_APP_GIT_REVISION: string; 15 | 16 | readonly REACT_APP_USE_DASH_LOCALE: string; 17 | } 18 | 19 | interface ImportMeta { 20 | readonly env: ImportMetaEnv; 21 | } 22 | -------------------------------------------------------------------------------- /web/beacon-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /web/beacon-app/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'jsdom', 6 | globals: true, 7 | // reporters: ['html'], 8 | setupFiles: ['./vitest.setup.ts'], 9 | coverage: { 10 | provider: 'istanbul', 11 | }, 12 | // path resolution 13 | alias: { 14 | '@': './src', 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /web/beacon-app/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | import '@testing-library/jest-dom/extend-expect'; 7 | -------------------------------------------------------------------------------- /web/placeholder/landing/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/placeholder/landing/favicon.png -------------------------------------------------------------------------------- /web/placeholder/landing/rotational-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotationalio/ensign/2232bb99b2d0a5ac9a626352fb3e35cd0377b158/web/placeholder/landing/rotational-logo.png --------------------------------------------------------------------------------