├── .dockerignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ └── trunk.yml ├── .gitignore ├── .nvmrc ├── .trunk ├── .gitignore ├── configs │ ├── .checkov.yaml │ ├── .hadolint.yaml │ ├── .markdownlint.json │ ├── .prettierrc │ ├── .shellcheckrc │ ├── .yamllint.yaml │ ├── biome.json │ └── svgo.config.mjs └── trunk.yaml ├── .vscode ├── extensions.json └── settings.json ├── Dockerfile ├── INSTRUCTIONS.md ├── LICENSE ├── Makefile ├── README.md ├── Vagrantfile ├── client ├── .gitignore ├── README.md ├── config │ ├── env.js │ ├── getHttpsConfig.js │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── modules.js │ ├── paths.js │ ├── pnpTs.js │ ├── polyfills.js │ ├── webpack.config.dev.js │ ├── webpack.config.js │ └── webpackDevServer.config.js ├── package-lock.json ├── package.json ├── public │ ├── 3rdpartystatic │ │ ├── cdnjs.cloudflare.com │ │ │ └── ajax │ │ │ │ └── libs │ │ │ │ └── codemirror │ │ │ │ └── 5.25.2 │ │ │ │ └── theme │ │ │ │ └── neo.css │ │ └── font-awesome-4.7.0 │ │ │ ├── HELP-US-OUT.txt │ │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ │ ├── less │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ │ └── scss │ │ │ ├── _animated.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ └── font-awesome.scss │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.html │ ├── loader.html │ ├── loader.js │ └── manifest.json ├── scripts │ ├── build.js │ ├── start.js │ └── test.js └── src │ ├── actions │ ├── backup.js │ ├── cluster.js │ ├── connection.js │ ├── frames.js │ ├── migration.js │ ├── query.js │ └── ui.js │ ├── assets │ ├── css │ │ ├── App.scss │ │ ├── Codemirror.scss │ │ ├── Editor.scss │ │ ├── EditorPanel.scss │ │ ├── EntitySelector.scss │ │ ├── Frames.scss │ │ ├── Graph.scss │ │ ├── Navbar.scss │ │ ├── NodeProperties.scss │ │ ├── PreviousQuery.scss │ │ ├── Properties.scss │ │ ├── Response.scss │ │ ├── ResponseInfo.scss │ │ ├── Scratchpad.scss │ │ ├── SessionFooterProperties.scss │ │ ├── Sidebar.scss │ │ └── bootstrap-colors.scss │ └── images │ │ ├── dgraph.png │ │ ├── diggy.png │ │ ├── graph.png │ │ ├── graph.svg │ │ ├── hourglass.svg │ │ ├── load.svg │ │ ├── loader.svg │ │ ├── logo.svg │ │ └── tree.png │ ├── components │ ├── ACL │ │ ├── AclPage.js │ │ ├── AclPage.scss │ │ ├── CreateGroupModal.js │ │ ├── EditUserModal.js │ │ ├── GqlDataAdapter.js │ │ ├── GroupDetailsPane.js │ │ ├── JsonDataAdapter.js │ │ └── UserDetailsPane.js │ ├── AutosizeGrid.js │ ├── Backups │ │ ├── ConfirmBackupModal.js │ │ ├── RadioSelect.js │ │ ├── StartBackupModal.js │ │ ├── backupModel.js │ │ ├── index.js │ │ └── index.scss │ ├── Cluster │ │ ├── ClusterPage.js │ │ ├── ClusterPage.scss │ │ ├── MoveTabletModal.js │ │ └── RemoveNodeModal.js │ ├── ConsolePage │ │ ├── GeoView.js │ │ └── GeoView.scss │ ├── D3Graph │ │ ├── D3Graph.scss │ │ └── index.js │ ├── DataExplorer │ │ ├── PathDisplay.js │ │ ├── index.js │ │ └── index.scss │ ├── EdgeProperties.js │ ├── EditorPanel.js │ ├── EntitySelector.js │ ├── FrameCodeTab.js │ ├── FrameItem.js │ ├── FrameLayout │ │ ├── FrameBodyToolbar.js │ │ ├── FrameErrorMessage.js │ │ ├── FrameHeader.js │ │ ├── FrameHeader.scss │ │ ├── FrameHistoric.js │ │ ├── FrameMessage.js │ │ ├── FrameSession.js │ │ ├── QueryPreview.js │ │ ├── SessionFooter.js │ │ ├── SessionFooterProperties.js │ │ ├── SessionFooterResult.js │ │ ├── SharingSettings.js │ │ └── SharingSettings.scss │ ├── FrameList.js │ ├── FrameLoading.js │ ├── GraphContainer.js │ ├── GraphIcon.js │ ├── HealthDot │ │ ├── index.js │ │ └── index.scss │ ├── Label.js │ ├── LicenseWarning │ │ ├── LicenseWarning.scss │ │ └── index.js │ ├── MovablePanel.js │ ├── NodeProperties.js │ ├── PanelLayout │ │ ├── HorizontalPanelLayout.js │ │ ├── PanelLayout.scss │ │ ├── VerticalPanelLayout.js │ │ ├── VerticalPanelLayout.scss │ │ └── index.js │ ├── PartialRenderInfo.js │ ├── PredicateSearchBar.js │ ├── Progress.js │ ├── Properties.js │ ├── QueryVarsEditor │ │ ├── index.js │ │ └── index.scss │ ├── QueryView │ │ ├── QueryView.scss │ │ └── index.js │ ├── SantaHat.js │ ├── ServerConnectionModal.js │ ├── ServerConnectionModal.scss │ ├── ServerLoginWidget.js │ ├── SessionList.js │ ├── Sidebar.js │ ├── SidebarInfo.js │ ├── TreeIcon.js │ ├── WizardSteps │ │ ├── index.js │ │ └── index.scss │ ├── ZeroUrlWidget.js │ └── schema │ │ ├── EditTypeModal.js │ │ ├── PredicatePropertiesPanel.js │ │ ├── PredicateTabs.js │ │ ├── PredicatesTable.js │ │ ├── SampleDataPanel │ │ ├── SamplesTable.js │ │ ├── SamplesTable.test.js │ │ ├── index.js │ │ └── index.scss │ │ ├── Schema.js │ │ ├── Schema.scss │ │ ├── SchemaDropDataModal.js │ │ ├── SchemaPredicateForm.js │ │ ├── SchemaPredicateModal.js │ │ ├── SchemaRawModeModal.js │ │ ├── TypeProperties.js │ │ └── TypesTable.js │ ├── containers │ ├── App.js │ ├── AppProvider.js │ ├── CodeMirror.js │ └── Editor.js │ ├── e2etests │ ├── .env │ ├── README.md │ ├── acl-secret.txt │ ├── acl │ │ ├── accessDenied.test.js │ │ ├── aclHelpers.js │ │ ├── changePermissions.test.js │ │ ├── cliUser.test.js │ │ ├── createUser.DISABLED-test.js │ │ ├── dgraphType.DISABLED-test.js │ │ ├── loginLogout.test.js │ │ ├── passwordCheck.test.js │ │ └── showGroot.test.js │ ├── basic.test.js │ ├── bigInteger.test.js │ ├── dataExplorer.test.js-DISABLED │ ├── docker-compose.dev.yml │ ├── docker-compose.prod.yml │ ├── expandGraph.test.js │ ├── jsonMutation.test.js │ ├── noDuplicateMutations.test.js │ ├── oneToOne.test.js │ ├── pptr.Dockerfile │ ├── puppetHelpers.js │ ├── queryTimeout.test.js │ └── typeSystem.test.js │ ├── index.js │ ├── index.test.js │ ├── lib │ ├── ColorGenerator.js │ ├── GraphLabeler.js │ ├── SchemaGraphParser.js │ ├── constants.js │ ├── dgraph-syntax.js │ ├── graph.js │ ├── graph.test.js │ ├── helpers.js │ ├── parsers │ │ └── queryVars.js │ ├── test_data │ │ ├── 10_movies_with_countries.json │ │ ├── small_graph_1.json │ │ ├── star_wars_colors_1.json │ │ └── star_wars_colors_2.json │ └── utils.js │ ├── reducers │ ├── backup.js │ ├── cluster.js │ ├── connection.js │ ├── frames.js │ ├── index.js │ ├── query.js │ └── ui.js │ ├── reportWebVitals.js │ └── setupTests.js ├── dev ├── Dockerfile ├── readme.md └── run.sh ├── docker-compose.yml ├── go.mod ├── main.go ├── scripts ├── build.prod.sh ├── functions.sh ├── provision.sh └── test.sh ├── server ├── buffer.go ├── content.go ├── play-dgraph-io.nginx.conf ├── server.go └── validate.go └── test.Dockerfile /.dockerignore: -------------------------------------------------------------------------------- 1 | build/ 2 | client/node_modules/ 3 | .DS_Store 4 | .vagrant 5 | dev/ 6 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS info: https://help.github.com/en/articles/about-code-owners 2 | # Owners are automatically requested for review for PRs that changes code 3 | # that they own. 4 | * @hypermodeinc/database 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | ## Expected behavior 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | ## Screenshots 27 | 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | ## Environment 31 | 32 | - OS: [e.g. macOS, Windows, Ubuntu] 33 | - Language [e.g. AssemblyScript, Go] 34 | - Version [e.g. v0.xx] 35 | 36 | ## Additional context 37 | 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Dgraph Community Support 4 | url: https://discord.hypermode.com 5 | about: Please ask and answer questions here 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Describe the solution you'd like 14 | 15 | A clear and concise description of what you want to happen. 16 | 17 | ## Describe alternatives you've considered 18 | 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | ## Additional context 22 | 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | Please explain the changes you made here. 4 | 5 | **Checklist** 6 | 7 | - [ ] Code compiles correctly and linting passes locally 8 | - [ ] For all _code_ changes, an entry added to the `CHANGELOG.md` file describing and linking to 9 | this PR 10 | - [ ] Tests added for new functionality, or regression tests for bug fixes added as applicable 11 | - [ ] For public APIs, new features, etc., PR on [docs repo](https://github.com/hypermodeinc/docs) 12 | staged and linked here 13 | 14 | **Instructions** 15 | 16 | - The PR title should follow the [Conventional Commits](https://www.conventionalcommits.org/) 17 | syntax, leading with `fix:`, `feat:`, `chore:`, `ci:`, etc. 18 | - The description should briefly explain what the PR is about. In the case of a bugfix, describe or 19 | link to the bug. 20 | - In the checklist section, check the boxes in that are applicable, using `[x]` syntax. If not 21 | applicable, remove the entire line. Only leave the box unchecked if you intend to come back and 22 | check the box later. 23 | - Delete the `Instructions` line and everything below it, to indicate you have read and are 24 | following these instructions. 🙂 25 | 26 | Thank you for your contribution to Dgraph! 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 22 23 | 24 | - name: Install Dependencies with Legacy Peer Deps 25 | run: npm install --legacy-peer-deps 26 | working-directory: ./client 27 | 28 | - name: Build Project with OpenSSL Legacy Provider 29 | run: | 30 | export NODE_OPTIONS=--openssl-legacy-provider 31 | npm run build 32 | working-directory: ./client 33 | -------------------------------------------------------------------------------- /.github/workflows/trunk.yml: -------------------------------------------------------------------------------- 1 | name: Trunk Code Quality 2 | on: 3 | pull_request: 4 | branches: main 5 | 6 | permissions: 7 | contents: read 8 | actions: write 9 | checks: write 10 | 11 | jobs: 12 | trunk-code-quality: 13 | name: Trunk Code Quality 14 | uses: hypermodeinc/.github/.github/workflows/trunk.yml@main 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Build files 14 | build/ 15 | 16 | # Bindata file 17 | server/bindata.go 18 | 19 | # Ratel2 20 | ratel2/node_modules/ 21 | 22 | .DS_Store 23 | .vagrant 24 | 25 | # Dgraph 26 | *.vlog 27 | *.sst 28 | LOCK 29 | KEYREGISTRY 30 | MANIFEST 31 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.17.0 2 | -------------------------------------------------------------------------------- /.trunk/.gitignore: -------------------------------------------------------------------------------- 1 | *out 2 | *logs 3 | *actions 4 | *notifications 5 | *tools 6 | plugins 7 | user_trunk.yaml 8 | user.yaml 9 | tmp 10 | -------------------------------------------------------------------------------- /.trunk/configs/.checkov.yaml: -------------------------------------------------------------------------------- 1 | skip-check: 2 | - CKV_GHA_7 3 | -------------------------------------------------------------------------------- /.trunk/configs/.hadolint.yaml: -------------------------------------------------------------------------------- 1 | # Following source doesn't work in most setups 2 | ignored: 3 | - SC1090 4 | - SC1091 5 | -------------------------------------------------------------------------------- /.trunk/configs/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "line-length": { "line_length": 150, "tables": false }, 3 | "no-inline-html": false, 4 | "no-bare-urls": false, 5 | "no-space-in-emphasis": false, 6 | "no-emphasis-as-heading": false, 7 | "first-line-heading": false 8 | } 9 | -------------------------------------------------------------------------------- /.trunk/configs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "proseWrap": "always", 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /.trunk/configs/.shellcheckrc: -------------------------------------------------------------------------------- 1 | enable=all 2 | source-path=SCRIPTDIR 3 | disable=SC2154 4 | 5 | # If you're having issues with shellcheck following source, disable the errors via: 6 | # disable=SC1090 7 | # disable=SC1091 8 | -------------------------------------------------------------------------------- /.trunk/configs/.yamllint.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | quoted-strings: 3 | required: only-when-needed 4 | extra-allowed: ["{|}"] 5 | key-duplicates: {} 6 | octal-values: 7 | forbid-implicit-octal: true 8 | -------------------------------------------------------------------------------- /.trunk/configs/svgo.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: [ 3 | { 4 | name: 'preset-default', 5 | params: { 6 | overrides: { 7 | removeViewBox: false, // https://github.com/svg/svgo/issues/1128 8 | sortAttrs: true, 9 | removeOffCanvasPaths: true, 10 | }, 11 | }, 12 | }, 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /.trunk/trunk.yaml: -------------------------------------------------------------------------------- 1 | # This file controls the behavior of Trunk: https://docs.trunk.io/cli 2 | # To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml 3 | version: 0.1 4 | cli: 5 | version: 1.22.15 6 | # Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) 7 | plugins: 8 | sources: 9 | - id: trunk 10 | ref: v1.6.8 11 | uri: https://github.com/trunk-io/plugins 12 | # Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) 13 | runtimes: 14 | enabled: 15 | - go@1.21.0 16 | - node@18.20.5 17 | - python@3.10.8 18 | # This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) 19 | lint: 20 | ignore: 21 | - linters: [prettier] 22 | paths: 23 | - "**/*.ts" 24 | - "**/*.tsx" 25 | - "**/*.js" 26 | - "**/*.jsx" 27 | - "**/*.mjs" 28 | - "**/*.css" 29 | - "**/*.html" 30 | - "**/*.json" 31 | enabled: 32 | - biome@1.9.4 33 | - golangci-lint2@2.1.6 34 | - trivy@0.62.1 35 | - renovate@40.0.6 36 | - actionlint@1.7.7 37 | - checkov@3.2.416 38 | - dotenv-linter@3.3.0 39 | - git-diff-check 40 | - gofmt@1.20.4 41 | - golangci-lint@1.64.8 42 | - hadolint@2.12.1-beta 43 | - markdownlint@0.44.0 44 | - osv-scanner@2.0.2 45 | - oxipng@9.1.5 46 | - prettier@3.5.3 47 | - shellcheck@0.10.0 48 | - shfmt@3.6.0 49 | - svgo@3.3.2 50 | - trufflehog@3.88.29 51 | - yamllint@1.37.1 52 | actions: 53 | enabled: 54 | - trunk-announce 55 | - trunk-check-pre-push 56 | - trunk-fmt-pre-commit 57 | - trunk-upgrade-available 58 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["trunk.io"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "trunk.io", 4 | "editor.trimAutoWhitespace": true, 5 | "trunk.autoInit": false 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ###### 2 | # Build Client 3 | #################### 4 | FROM node:14.17.0-alpine as client 5 | 6 | RUN apk update && apk --no-cache --virtual build-dependencies add make git bash python3 gcc g++ 7 | 8 | # build package manifest layer 9 | RUN mkdir -p /ratel/client 10 | WORKDIR /ratel/client 11 | COPY ./client/package.json /ratel/client 12 | RUN npm install --legacy-peer-deps --no-optional 13 | 14 | # copy all assets and build 15 | COPY . /ratel 16 | RUN npm run build:prod 17 | 18 | ###### 19 | # Build Server 20 | #################### 21 | FROM golang:1.16.4-alpine as server 22 | 23 | RUN apk update && apk add git bash 24 | COPY . /ratel 25 | 26 | WORKDIR /ratel 27 | ENV CGO_ENABLED=0 28 | COPY --from=client /ratel/client/build /ratel/client/build 29 | # instal go-bindata 30 | RUN go get -u github.com/go-bindata/go-bindata/... 31 | RUN ./scripts/build.prod.sh --server 32 | 33 | ###### 34 | # Final Image 35 | #################### 36 | FROM alpine:latest as final 37 | 38 | RUN apk add --no-cache ca-certificates 39 | RUN addgroup -g 1000 dgraph && \ 40 | adduser -u 1000 -G dgraph -s /bin/sh -D dgraph 41 | # copy server artifact w/ embedded client artifact (bindata) to final stage 42 | COPY --from=server /ratel/build/ratel /usr/local/bin/dgraph-ratel 43 | EXPOSE 8000 44 | USER dgraph 45 | 46 | CMD ["/usr/local/bin/dgraph-ratel"] 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-FileCopyrightText: © Hypermode Inc. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | BUILD ?= $(shell git rev-parse --short HEAD) 7 | BUILD_CODENAME = unnamed 8 | BUILD_DATE ?= $(shell git log -1 --format=%ci) 9 | BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) 10 | BUILD_VERSION ?= $(shell git describe --always --tags) 11 | 12 | MODIFIED = $(shell git diff-index --quiet HEAD || echo "-mod") 13 | 14 | .PHONY: version test build latest release push help 15 | 16 | version: 17 | @echo Ratel ${BUILD_VERSION} 18 | @echo Build: ${BUILD} 19 | @echo Codename: ${BUILD_CODENAME}${MODIFIED} 20 | @echo Build date: ${BUILD_DATE} 21 | @echo Branch: ${BUILD_BRANCH} 22 | # requires GNU grep 23 | @echo Go version: $(shell printf "go-%s" `grep -oP '(?<=golang:)[^-]*' Dockerfile`) 24 | 25 | test: 26 | @echo Running Tests 27 | @scripts/test.sh 28 | 29 | build: 30 | @docker build -f Dockerfile -t dgraph/ratel:${BUILD_VERSION} . 31 | 32 | latest: build 33 | @docker tag dgraph/ratel:${BUILD_VERSION} dgraph/ratel:latest 34 | 35 | release: push latest 36 | @docker push dgraph/ratel:latest 37 | 38 | push: 39 | @docker push dgraph/ratel:${BUILD_VERSION} 40 | 41 | help: 42 | @echo 43 | @echo Build commands: 44 | @echo " make build - Build docker image" 45 | @echo " make test - Runs tests" 46 | @echo " make push - Push Docker Image with the current version tag to Docker Hub" 47 | @echo " make latest - Tag the current Docker image as 'latest'" 48 | @echo " make release - Push Docker image with the current version and 'latest' tags" 49 | @echo " make help - This help" 50 | @echo 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ratel 2 | 3 | Dgraph IDE, available at https://ratel.hypermode.com. 4 | 5 | ## Building and running 6 | 7 | See [Instructions](./INSTRUCTIONS.md). 8 | 9 | ## License 10 | 11 | Apache 2.0. See [LICENSE](./LICENSE). 12 | 13 | ## Note 14 | 15 | We used to run Ratel along with the main Dgraph binary(Core Code). But it has been removed and some 16 | code here has become obsolete. Like the ones in the "Server" directory, some processes in Bash 17 | Script and so on. Now we have created a unique image for Ratel. See 18 | https://hub.docker.com/r/dgraph/ratel 19 | 20 | ## play.dgraph.io 21 | 22 | The UI available at http://play.dgraph.io/ is kept in an S3 Bucket and distributed via CDN. 23 | Communication between the UI, documentation and parts of the Tour uses a shared dataset. Its 24 | configuration is done through the config file in `./server/play-dgraph-io.nginx.conf`. 25 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | ###### 5 | # Vagrant solution to quickly test Linux CI environment 6 | # Instructions: 7 | # vagrant up && vagrant ssh 8 | # pushd ~/ratel/client/ && npm install --legacy-peer-deps && popd 9 | # cd ratel && make test 10 | ############ 11 | Vagrant.configure("2") do |config| 12 | config.vm.box = "generic/ubuntu2004" 13 | config.vm.hostname = "ratel.test" 14 | # use rsync to avoid mac vs linux node_modules and symlink issues 15 | config.vm.synced_folder ".", "/home/vagrant/ratel", type: "rsync", 16 | rsync__exclude: ["./client/node_modules"] 17 | 18 | # give some memory for npm install 19 | config.vm.provider "virtualbox" do |vbox| 20 | vbox.memory = 2048 21 | vbox.cpus = 2 22 | end 23 | 24 | # install docker, docker-compose, nodejs, puppeteer requirements 25 | config.vm.provision "shell", path: "./scripts/provision.sh" 26 | config.vm.provision "shell", inline: "usermod -aG docker vagrant" 27 | end 28 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs/ 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Dependencies 9 | node_modules/ 10 | /.pnp 11 | .pnp.js 12 | 13 | # Optional npm cache directory 14 | .npm 15 | 16 | # testing 17 | /coverage 18 | 19 | # production 20 | /build 21 | 22 | # VS Code 23 | .vscode/ 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | *.code-workspace 29 | 30 | # JetBrains IDEs 31 | .idea/ 32 | /*.iml 33 | 34 | # Misc 35 | .DS_Store 36 | Thumbs.db 37 | .env.local 38 | .env.development.local 39 | .env.test.local 40 | .env.production.local 41 | 42 | # Build files 43 | build/ 44 | 45 | # CSS (preprocessed by scss) 46 | /src/assets/css/*.css 47 | 48 | # TODO Notes 49 | /*.todo -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Dgraph Dashboard UI 2 | 3 | This project was bootstrapped with 4 | [Create React App](https://github.com/facebookincubator/create-react-app). 5 | 6 | ## Setting up 7 | 8 | The following steps would help setup the app for development locally. 9 | 10 | 1. Make sure you have [Node.js](https://nodejs.org/en/) installed. 11 | 2. [Install and run](https://docs.dgraph.io) Dgraph on the default port(8080) so that the frontend 12 | can communicate with it. 13 | 3. We use [npm](https://www.npmjs.com/) for dependency management. Run 14 | `npm install --legacy-peer-deps` from within the dashboard folder to install the deps. 15 | 4. Run `npm start` which would open up the UI at `http://localhost:3000`.The UI gets refreshed 16 | automatically after a change in any files inside the src folder. 17 | 18 | You can run `npm run build` to generate the bundled files for production and push them with your 19 | development changes. These files are served by Dgraph on the default port at 20 | `http://localhost:8080`. 21 | -------------------------------------------------------------------------------- /client/config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | 'use strict' 7 | 8 | const fs = require('fs') 9 | const path = require('path') 10 | const crypto = require('crypto') 11 | const chalk = require('react-dev-utils/chalk') 12 | const paths = require('./paths') 13 | 14 | // Ensure the certificate and key provided are valid and if not 15 | // throw an easy to debug error 16 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 17 | let encrypted 18 | try { 19 | // publicEncrypt will throw an error with an invalid cert 20 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')) 21 | } catch (err) { 22 | throw new Error( 23 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`, 24 | ) 25 | } 26 | 27 | try { 28 | // privateDecrypt will throw an error with an invalid key 29 | crypto.privateDecrypt(key, encrypted) 30 | } catch (err) { 31 | throw new Error( 32 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 33 | err.message 34 | }`, 35 | ) 36 | } 37 | } 38 | 39 | // Read file and throw an error if it doesn't exist 40 | function readEnvFile(file, type) { 41 | if (!fs.existsSync(file)) { 42 | throw new Error( 43 | `You specified ${chalk.cyan( 44 | type, 45 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.`, 46 | ) 47 | } 48 | return fs.readFileSync(file) 49 | } 50 | 51 | // Get the https config 52 | // Return cert files if provided in env, otherwise just true or false 53 | function getHttpsConfig() { 54 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env 55 | const isHttps = HTTPS === 'true' 56 | 57 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 58 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE) 59 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE) 60 | const config = { 61 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 62 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 63 | } 64 | 65 | validateKeyAndCerts({ ...config, keyFile, crtFile }) 66 | return config 67 | } 68 | return isHttps 69 | } 70 | 71 | module.exports = getHttpsConfig 72 | -------------------------------------------------------------------------------- /client/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | // This is a custom Jest transformer turning style imports into empty objects. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process() { 11 | return 'module.exports = {};' 12 | }, 13 | getCacheKey() { 14 | // The output is always the same. 15 | return 'cssTransform' 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /client/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | const path = require('path') 7 | const camelcase = require('camelcase') 8 | 9 | // This is a custom Jest transformer turning file imports into filenames. 10 | // http://facebook.github.io/jest/docs/en/webpack.html 11 | 12 | module.exports = { 13 | process(src, filename) { 14 | const assetFilename = JSON.stringify(path.basename(filename)) 15 | 16 | if (filename.match(/\.svg$/)) { 17 | // Based on how SVGR generates a component name: 18 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 19 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 20 | pascalCase: true, 21 | }) 22 | const componentName = `Svg${pascalCaseFilename}` 23 | return `const React = require('react'); 24 | module.exports = { 25 | __esModule: true, 26 | default: ${assetFilename}, 27 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 28 | return { 29 | $$typeof: Symbol.for('react.element'), 30 | type: 'svg', 31 | ref: ref, 32 | key: null, 33 | props: Object.assign({}, props, { 34 | children: ${assetFilename} 35 | }) 36 | }; 37 | }), 38 | };` 39 | } 40 | 41 | return `module.exports = ${assetFilename};` 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /client/config/pnpTs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | const { resolveModuleName } = require('ts-pnp') 7 | 8 | exports.resolveModuleName = ( 9 | typescript, 10 | moduleName, 11 | containingFile, 12 | compilerOptions, 13 | resolutionHost, 14 | ) => { 15 | return resolveModuleName( 16 | moduleName, 17 | containingFile, 18 | compilerOptions, 19 | resolutionHost, 20 | typescript.resolveModuleName, 21 | ) 22 | } 23 | 24 | exports.resolveTypeReferenceDirective = ( 25 | typescript, 26 | moduleName, 27 | containingFile, 28 | compilerOptions, 29 | resolutionHost, 30 | ) => { 31 | return resolveModuleName( 32 | moduleName, 33 | containingFile, 34 | compilerOptions, 35 | resolutionHost, 36 | typescript.resolveTypeReferenceDirective, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /client/config/polyfills.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | if (typeof Promise === 'undefined') { 7 | // Rejection tracking prevents a common issue where React gets into an 8 | // inconsistent state due to an error, but it gets swallowed by a Promise, 9 | // and the user has no idea what causes React's erratic future behavior. 10 | require('promise/lib/rejection-tracking').enable() 11 | window.Promise = require('promise/lib/es6-extensions.js') 12 | } 13 | 14 | // fetch() polyfill for making API calls. 15 | require('whatwg-fetch') 16 | 17 | // Object.assign() is commonly used with React. 18 | // It will use the native implementation if it's present and isn't buggy. 19 | Object.assign = require('object-assign') 20 | 21 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 22 | // We don't polyfill it in the browser--this is user's responsibility. 23 | if (process.env.NODE_ENV === 'test') { 24 | require('raf').polyfill(global) 25 | } 26 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/cdnjs.cloudflare.com/ajax/libs/codemirror/5.25.2/theme/neo.css: -------------------------------------------------------------------------------- 1 | /* neo theme for codemirror */ 2 | 3 | /* Color scheme */ 4 | 5 | .cm-s-neo.CodeMirror { 6 | background-color: #ffffff; 7 | color: #2e383c; 8 | line-height: 1.4375; 9 | } 10 | .cm-s-neo .cm-comment { 11 | color: #75787b; 12 | } 13 | .cm-s-neo .cm-keyword, 14 | .cm-s-neo .cm-property { 15 | color: #1d75b3; 16 | } 17 | .cm-s-neo .cm-atom, 18 | .cm-s-neo .cm-number { 19 | color: #75438a; 20 | } 21 | .cm-s-neo .cm-node, 22 | .cm-s-neo .cm-tag { 23 | color: #9c3328; 24 | } 25 | .cm-s-neo .cm-string { 26 | color: #b35e14; 27 | } 28 | .cm-s-neo .cm-variable, 29 | .cm-s-neo .cm-qualifier { 30 | color: #047d65; 31 | } 32 | 33 | /* Editor styling */ 34 | 35 | .cm-s-neo pre { 36 | padding: 0; 37 | } 38 | 39 | .cm-s-neo .CodeMirror-gutters { 40 | border: none; 41 | border-right: 10px solid transparent; 42 | background-color: transparent; 43 | } 44 | 45 | .cm-s-neo .CodeMirror-linenumber { 46 | padding: 0; 47 | color: #e0e2e5; 48 | } 49 | 50 | .cm-s-neo .CodeMirror-guttermarker { 51 | color: #1d75b3; 52 | } 53 | .cm-s-neo .CodeMirror-guttermarker-subtle { 54 | color: #e0e2e5; 55 | } 56 | 57 | .cm-s-neo .CodeMirror-cursor { 58 | width: auto; 59 | border: 0; 60 | background: rgba(155, 157, 162, 0.37); 61 | z-index: 1; 62 | } 63 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/3rdpartystatic/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/3rdpartystatic/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: 0.2em 0.25em 0.15em; 6 | border: solid 0.08em $fa-border-color; 7 | border-radius: 0.1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { 11 | float: left; 12 | } 13 | .#{$fa-css-prefix}-pull-right { 14 | float: right; 15 | } 16 | 17 | .#{$fa-css-prefix} { 18 | &.#{$fa-css-prefix}-pull-left { 19 | margin-right: 0.3em; 20 | } 21 | &.#{$fa-css-prefix}-pull-right { 22 | margin-left: 0.3em; 23 | } 24 | } 25 | 26 | /* Deprecated as of 4.4.0 */ 27 | .pull-right { 28 | float: right; 29 | } 30 | .pull-left { 31 | float: left; 32 | } 33 | 34 | .#{$fa-css-prefix} { 35 | &.pull-left { 36 | margin-right: 0.3em; 37 | } 38 | &.pull-right { 39 | margin-left: 0.3em; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { 11 | font-size: 2em; 12 | } 13 | .#{$fa-css-prefix}-3x { 14 | font-size: 3em; 15 | } 16 | .#{$fa-css-prefix}-4x { 17 | font-size: 4em; 18 | } 19 | .#{$fa-css-prefix}-5x { 20 | font-size: 5em; 21 | } 22 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { 9 | position: relative; 10 | } 11 | } 12 | .#{$fa-css-prefix}-li { 13 | position: absolute; 14 | left: -$fa-li-width; 15 | width: $fa-li-width; 16 | top: (2em / 14); 17 | text-align: center; 18 | &.#{$fa-css-prefix}-lg { 19 | left: -$fa-li-width + (4em / 14); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | @mixin fa-icon-rotate($degrees, $rotation) { 14 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 15 | -webkit-transform: rotate($degrees); 16 | -ms-transform: rotate($degrees); 17 | transform: rotate($degrees); 18 | } 19 | 20 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 21 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 22 | -webkit-transform: scale($horiz, $vert); 23 | -ms-transform: scale($horiz, $vert); 24 | transform: scale($horiz, $vert); 25 | } 26 | 27 | // Only display content to screen readers. A la Bootstrap 4. 28 | // 29 | // See: http://a11yproject.com/posts/how-to-hide-content/ 30 | 31 | @mixin sr-only { 32 | position: absolute; 33 | width: 1px; 34 | height: 1px; 35 | padding: 0; 36 | margin: -1px; 37 | overflow: hidden; 38 | clip: rect(0, 0, 0, 0); 39 | border: 0; 40 | } 41 | 42 | // Use in conjunction with .sr-only to only display content when it's focused. 43 | // 44 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 45 | // 46 | // Credit: HTML5 Boilerplate 47 | 48 | @mixin sr-only-focusable { 49 | &:active, 50 | &:focus { 51 | position: static; 52 | width: auto; 53 | height: auto; 54 | margin: 0; 55 | overflow: visible; 56 | clip: auto; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: "FontAwesome"; 6 | src: url("#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}"); 7 | src: 8 | url("#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}") 9 | format("embedded-opentype"), 10 | url("#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}") format("woff2"), 11 | url("#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}") format("woff"), 12 | url("#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}") format("truetype"), 13 | url("#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular") 14 | format("svg"); 15 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { 5 | @include fa-icon-rotate(90deg, 1); 6 | } 7 | .#{$fa-css-prefix}-rotate-180 { 8 | @include fa-icon-rotate(180deg, 2); 9 | } 10 | .#{$fa-css-prefix}-rotate-270 { 11 | @include fa-icon-rotate(270deg, 3); 12 | } 13 | 14 | .#{$fa-css-prefix}-flip-horizontal { 15 | @include fa-icon-flip(-1, 1, 0); 16 | } 17 | .#{$fa-css-prefix}-flip-vertical { 18 | @include fa-icon-flip(1, -1, 2); 19 | } 20 | 21 | // Hook for IE8-9 22 | // ------------------------- 23 | 24 | :root .#{$fa-css-prefix}-rotate-90, 25 | :root .#{$fa-css-prefix}-rotate-180, 26 | :root .#{$fa-css-prefix}-rotate-270, 27 | :root .#{$fa-css-prefix}-flip-horizontal, 28 | :root .#{$fa-css-prefix}-flip-vertical { 29 | filter: none; 30 | } 31 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { 5 | @include sr-only(); 6 | } 7 | .sr-only-focusable { 8 | @include sr-only-focusable(); 9 | } 10 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, 13 | .#{$fa-css-prefix}-stack-2x { 14 | position: absolute; 15 | left: 0; 16 | width: 100%; 17 | text-align: center; 18 | } 19 | .#{$fa-css-prefix}-stack-1x { 20 | line-height: inherit; 21 | } 22 | .#{$fa-css-prefix}-stack-2x { 23 | font-size: 2em; 24 | } 25 | .#{$fa-css-prefix}-inverse { 26 | color: $fa-inverse; 27 | } 28 | -------------------------------------------------------------------------------- /client/public/3rdpartystatic/font-awesome-4.7.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /client/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/favicon-16x16.png -------------------------------------------------------------------------------- /client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/loader.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Choose a version of the Ratel interface

8 |
9 |
10 |
11 |
Dev
12 |
13 | Bleeding Edge. Unstable 14 |
15 | 16 |

17 | Includes latest unreleased improvements, features, experiments, 18 | sometimes bugs. Feedback is welcome in 19 | Discuss 20 |

21 | 22 | Launch Dev 23 | 24 |
25 |
26 | 27 | 44 | 45 |
46 |
47 |
Local Bundle
48 |
49 | Works Offline. Never auto-updates 50 |
51 | 52 |

53 | This version of the UI was compiled into your Ratel binary. It 54 | doesn't require internet connection to run, but will never get 55 | updated unless you install a new version of Ratel. 56 |

57 | 58 | Launch Offline 59 | 60 |
61 |
62 |
63 | 64 |
65 | 66 | 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /client/public/loader.js: -------------------------------------------------------------------------------- 1 | var COOKIE_VALUE = 'latest' 2 | 3 | var cookieCheckbox = document.getElementById('cookieCheckbox') 4 | 5 | cookieCheckbox.checked = 6 | localStorage.getItem(RATEL_AUTOLOAD_URL_STORAGE_KEY) == COOKIE_VALUE 7 | 8 | cookieCheckbox.addEventListener('change', function onChange(evt) { 9 | localStorage.setItem( 10 | RATEL_AUTOLOAD_URL_STORAGE_KEY, 11 | evt.target.checked ? COOKIE_VALUE : '', 12 | ) 13 | }) 14 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Dgraph Ratel Dashboard", 3 | "name": "Dgraph Ratel Dashboard", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "type": "image/x-icon" 8 | } 9 | ], 10 | "start_url": "./index.html", 11 | "display": "standalone", 12 | "theme_color": "#000000", 13 | "background_color": "#ffffff" 14 | } 15 | -------------------------------------------------------------------------------- /client/scripts/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | // Do this as the first thing so that any code reading it knows the right env. 7 | process.env.BABEL_ENV = 'test' 8 | process.env.NODE_ENV = 'test' 9 | process.env.PUBLIC_URL = '' 10 | 11 | // Makes the script crash on unhandled rejections instead of silently 12 | // ignoring them. In the future, promise rejections that are not handled will 13 | // terminate the Node.js process with a non-zero exit code. 14 | process.on('unhandledRejection', (err) => { 15 | throw err 16 | }) 17 | 18 | // Ensure environment variables are read. 19 | require('../config/env') 20 | 21 | const jest = require('jest') 22 | const execSync = require('child_process').execSync 23 | const argv = process.argv.slice(2) 24 | 25 | function isInGitRepository() { 26 | try { 27 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }) 28 | return true 29 | } catch (e) { 30 | return false 31 | } 32 | } 33 | 34 | function isInMercurialRepository() { 35 | try { 36 | execSync('hg --cwd . root', { stdio: 'ignore' }) 37 | return true 38 | } catch (e) { 39 | return false 40 | } 41 | } 42 | 43 | // Watch unless on CI or explicitly running all tests 44 | if ( 45 | !process.env.CI && 46 | argv.indexOf('--watchAll') === -1 && 47 | argv.indexOf('--watchAll=false') === -1 48 | ) { 49 | // https://github.com/facebook/create-react-app/issues/5210 50 | const hasSourceControl = isInGitRepository() || isInMercurialRepository() 51 | argv.push(hasSourceControl ? '--watch' : '--watchAll') 52 | } 53 | 54 | jest.run(argv) 55 | -------------------------------------------------------------------------------- /client/src/actions/backup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { callStartBackup } from 'components/Backups/backupModel' 7 | 8 | export const SET_BACKUP_CONFIG = 'frames/SET_BACKUP_CONFIG' 9 | export const SAVE_START_BACKUP = 'frames/SAVE_START_BACKUP ' 10 | export const SAVE_BACKUP_RESULT = 'frames/SAVE_BACKUP_RESULT' 11 | export const SAVE_BACKUP_ERROR = 'frames/SAVE_BACKUP_ERROR' 12 | 13 | export const DEFAULT_BACKUP_CONFIG = { 14 | backupPath: '', 15 | destinationType: 'nfs', 16 | forceFull: false, 17 | overrideCredentials: false, 18 | anonymous: false, 19 | } 20 | 21 | export function setBackupConfig(payload) { 22 | return { 23 | type: SET_BACKUP_CONFIG, 24 | payload, 25 | } 26 | } 27 | 28 | export const setBackupResult = (backupId, result) => ({ 29 | type: SAVE_BACKUP_RESULT, 30 | backupId, 31 | result, 32 | }) 33 | 34 | export const setBackupError = (backupId, err) => ({ 35 | type: SAVE_BACKUP_ERROR, 36 | backupId, 37 | err, 38 | }) 39 | 40 | export const startBackup = (serverUrl, config) => async (dispatch) => { 41 | const backupId = `${serverUrl} _ ${Date.now()}` 42 | dispatch({ 43 | type: SAVE_START_BACKUP, 44 | backupId, 45 | config, 46 | serverUrl, 47 | startTime: Date.now(), 48 | }) 49 | try { 50 | const res = await callStartBackup(config) 51 | dispatch(setBackupResult(backupId, res)) 52 | } catch (err) { 53 | dispatch(setBackupError(backupId, err)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/src/actions/cluster.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { getDgraphClientStub } from 'lib/helpers' 7 | 8 | export const GET_INSTANCE_HEALTH_RESULT = 'cluster/GET_INSTANCE_HEALTH_RESULT' 9 | export const GET_CLUSTER_STATE_RESULT = 'cluster/GET_CLUSTER_STATE_RESULT' 10 | export const SET_IS_AUTHORIZED = 'cluster/SET_IS_AUTHORIZED' 11 | 12 | export function getInstanceHealth() { 13 | return async (dispatch, getState) => { 14 | const clientStub = await getDgraphClientStub() 15 | 16 | try { 17 | const health = await clientStub.getHealth(true) 18 | dispatch(getInstanceHealthResult(health)) 19 | dispatch(setIsAuthorized(true)) 20 | } catch (err) { 21 | if (isPermissionError(err)) { 22 | dispatch(setIsAuthorized(false)) 23 | } 24 | dispatch(getInstanceHealthResult(null)) 25 | } 26 | } 27 | } 28 | 29 | export function getInstanceHealthResult(json) { 30 | return { 31 | type: GET_INSTANCE_HEALTH_RESULT, 32 | json, 33 | } 34 | } 35 | 36 | export function getClusterState() { 37 | return async (dispatch, getState) => { 38 | const client = await getDgraphClientStub() 39 | 40 | try { 41 | const clusterState = await client.getState() 42 | dispatch(getClusterStateResult(clusterState)) 43 | dispatch(setIsAuthorized(true)) 44 | } catch (err) { 45 | if (isPermissionError(err)) { 46 | dispatch(setIsAuthorized(false)) 47 | } 48 | dispatch(getClusterStateResult(null)) 49 | } 50 | } 51 | } 52 | 53 | export function getClusterStateResult(json) { 54 | return { 55 | type: GET_CLUSTER_STATE_RESULT, 56 | json, 57 | } 58 | } 59 | 60 | function isPermissionError(err) { 61 | const msg = err.errors?.[0]?.message || err.message 62 | if (msg && msg.indexOf('PermissionDenied') > 0) { 63 | return true 64 | } 65 | return false 66 | } 67 | 68 | export function setIsAuthorized(isAuthorized) { 69 | return { 70 | type: SET_IS_AUTHORIZED, 71 | isAuthorized, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client/src/actions/migration.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export const MIGRATE_TO_SERVER_CONNECTION = 7 | 'migration/MIGRATE_TO_SERVER_CONNECTION' 8 | 9 | export const MIGRATE_TO_HAVE_ZERO_URL = 'migration/MIGRATE_TO_HAVE_ZERO_URL' 10 | 11 | export function migrateToServerConnection() { 12 | return { type: MIGRATE_TO_SERVER_CONNECTION } 13 | } 14 | 15 | export function migrateToHaveZeroUrl() { 16 | return { type: MIGRATE_TO_HAVE_ZERO_URL } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/actions/query.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export const UPDATE_QUERY = 'query/UPDATE_QUERY' 7 | export const UPDATE_ACTION = 'query/UPDATE_ACTION' 8 | export const UPDATE_QUERY_AND_ACTION = 'query/UPDATE_QUERY_AND_ACTION' 9 | export const UPDATE_QUERY_VARS = 'query/UPDATE_QUERY_VARS' 10 | export const UPDATE_READ_ONLY = 'query/UPDATE_READ_ONLY' 11 | export const UPDATE_BEST_EFFORT = 'query/UPDATE_BEST_EFFORT' 12 | 13 | export function updateQuery(query) { 14 | return { 15 | type: UPDATE_QUERY, 16 | query, 17 | } 18 | } 19 | 20 | export function updateAction(action) { 21 | return { 22 | type: UPDATE_ACTION, 23 | action, 24 | } 25 | } 26 | 27 | export function updateQueryAndAction(query, action) { 28 | return { 29 | type: UPDATE_QUERY_AND_ACTION, 30 | query, 31 | action, 32 | } 33 | } 34 | 35 | export function updateReadOnly(readOnly) { 36 | return { 37 | type: UPDATE_READ_ONLY, 38 | readOnly, 39 | } 40 | } 41 | 42 | export function updateBestEffort(bestEffort) { 43 | return { 44 | type: UPDATE_BEST_EFFORT, 45 | bestEffort, 46 | } 47 | } 48 | 49 | export const updateQueryVars = (newVars) => ({ 50 | type: UPDATE_QUERY_VARS, 51 | newVars, 52 | }) 53 | -------------------------------------------------------------------------------- /client/src/actions/ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export const SET_PANEL_SIZE = 'ui/SET_PANEL_SIZE' 7 | export const SET_PANEL_MINIMIZED = 'ui/SET_PANEL_MINIMIZED' 8 | 9 | export const CLICK_SIDEBAR_URL = 'mainframe/CLICK_SIDEBAR_URL' 10 | 11 | export function clickSidebarUrl(url) { 12 | return { 13 | type: CLICK_SIDEBAR_URL, 14 | url, 15 | } 16 | } 17 | 18 | export function setPanelSize({ width, height }) { 19 | return { 20 | type: SET_PANEL_SIZE, 21 | width, 22 | height, 23 | } 24 | } 25 | 26 | export function setPanelMinimized(minimized) { 27 | return { 28 | type: SET_PANEL_MINIMIZED, 29 | minimized, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/src/assets/css/App.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | height: 100vh; 5 | overflow: hidden; 6 | width: 100vw; 7 | } 8 | 9 | // Fontawesome variables that the old Ratel depends on 10 | :root { 11 | --blue: #007bff; 12 | --indigo: #6610f2; 13 | --purple: #6f42c1; 14 | --pink: #e83e8c; 15 | --red: #dc3545; 16 | --orange: #fd7e14; 17 | --yellow: #ffc107; 18 | --green: #28a745; 19 | --teal: #20c997; 20 | --cyan: #17a2b8; 21 | --white: #fff; 22 | --gray: #6c757d; 23 | --gray-dark: #343a40; 24 | --primary: #007bff; 25 | --secondary: #6c757d; 26 | --success: #28a745; 27 | --info: #17a2b8; 28 | --warning: #ffc107; 29 | --danger: #dc3545; 30 | --light: #f8f9fa; 31 | --dark: #343a40; 32 | } 33 | 34 | #root { 35 | display: flex; 36 | flex-direction: row; 37 | height: 100vh; 38 | position: relative; 39 | } 40 | 41 | .click-capture { 42 | width: calc(100% - 358px); 43 | height: 100%; 44 | position: absolute; 45 | top: 0; 46 | z-index: 5; 47 | opacity: 0.76; 48 | background: white; 49 | } 50 | 51 | .main-content { 52 | background-color: #ececec; 53 | 54 | display: flex; 55 | // Stretch main content to occupy full remaining width of the page. 56 | flex-grow: 1; 57 | flex-direction: column; 58 | overflow: hidden; 59 | height: 100%; 60 | padding-left: 15px; 61 | } 62 | 63 | :focus { 64 | outline: none; 65 | } 66 | 67 | .vis-tooltip { 68 | border: 1px solid #f3d98c; 69 | background-color: #fffad5; 70 | padding: 10px; 71 | margin: 10px; 72 | border-radius: 3px; 73 | height: auto; 74 | position: absolute !important; 75 | } 76 | 77 | #properties em { 78 | height: 100%; 79 | } 80 | 81 | #properties pre { 82 | height: 100%; 83 | } 84 | 85 | input.form-control { 86 | font-size: 16px !important; 87 | } 88 | 89 | .form-group { 90 | margin-bottom: 5px; 91 | } 92 | 93 | // Override the library default. 94 | .hljs { 95 | background: none; 96 | } 97 | -------------------------------------------------------------------------------- /client/src/assets/css/EditorPanel.scss: -------------------------------------------------------------------------------- 1 | .editor-panel { 2 | border: 1px solid #d2d2d2; 3 | border-radius: 1px; 4 | 5 | // .editor-outer must grow to take up all our vertical space. 6 | display: flex; 7 | flex-direction: column; 8 | .editor-outer { 9 | flex: 1; 10 | } 11 | 12 | .header { 13 | border-bottom: 1px solid #d2d2d2; 14 | background-color: #fff; 15 | } 16 | 17 | .actions { 18 | float: left; 19 | &.right { 20 | float: right; 21 | } 22 | } 23 | 24 | .action { 25 | padding: 8px 10px; 26 | display: inline-block; 27 | border: 1px solid #d2d2d2; 28 | color: #d4d4d4; 29 | transition: color 0.2s; 30 | 31 | label { 32 | margin: 0; 33 | } 34 | 35 | &.actionable { 36 | background-color: #eee; 37 | color: #333; 38 | button { 39 | padding: 8px 10px; 40 | margin: -8px -10px; 41 | background-color: transparent; 42 | border: none; 43 | border-radius: 0; 44 | color: inherit; 45 | font-size: 14px; 46 | &::after { 47 | display: none; 48 | } 49 | } 50 | a input { 51 | pointer-events: none; 52 | } 53 | } 54 | 55 | &:not(.actionable) { 56 | cursor: default; 57 | } 58 | 59 | &.actionable:hover { 60 | background: #f7f7f7; 61 | } 62 | } 63 | } 64 | 65 | .action-list { 66 | list-style: none; 67 | } 68 | 69 | .action-list .action { 70 | display: inline-block; 71 | } 72 | 73 | .btn-dgraph { 74 | border: 1px solid #d2d1d1; 75 | background-color: white; 76 | color: #8a8a8a; 77 | } 78 | 79 | .editor-radio { 80 | padding: 5px; 81 | background: #f3f3f3; 82 | .editor-label { 83 | display: inline-flex; 84 | align-items: center; 85 | margin-bottom: 0px; 86 | } 87 | .editor-type { 88 | margin: 0px 5px; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /client/src/assets/css/EntitySelector.scss: -------------------------------------------------------------------------------- 1 | .entity-selector { 2 | background: white; 3 | border-top: 1px solid #d2d2d2; 4 | padding: 10px; 5 | position: relative; 6 | 7 | transition: all 200ms; 8 | overflow-y: auto; 9 | 10 | max-height: 85px; 11 | &.expanded { 12 | max-height: 205px; 13 | 14 | .toggle { 15 | transform-origin: center; 16 | transform: rotate3d(1, 0, 0, 180deg); 17 | } 18 | } 19 | 20 | .toggle { 21 | $size: 16px; 22 | $color: #848f99; 23 | background-color: #fff; 24 | border-color: $color; 25 | box-sizing: border-box; 26 | color: $color; 27 | font-size: $size - 4px; 28 | line-height: $size - 2px; 29 | height: $size; 30 | padding: 0; 31 | position: absolute; 32 | right: 6px; 33 | transition: transform 400ms; 34 | top: 6px; 35 | width: $size; 36 | } 37 | } 38 | 39 | .label-container { 40 | display: inline-block; 41 | padding: 1px 7px 1.5px; 42 | border-radius: 5px; 43 | margin-right: 7px; 44 | margin-bottom: 5px; 45 | font-size: 12px; 46 | font-weight: 500; 47 | 48 | .label-value { 49 | margin-right: 4px; 50 | } 51 | 52 | .shorthand { 53 | font-weight: 400; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/src/assets/css/Graph.scss: -------------------------------------------------------------------------------- 1 | .progress .progress-bar { 2 | -webkit-transition: none; 3 | -o-transition: none; 4 | transition: none; 5 | } 6 | 7 | .Graph-progress { 8 | top: 50%; 9 | margin: auto 10%; 10 | position: absolute; 11 | width: 80%; 12 | } 13 | 14 | .graph-container { 15 | display: flex; 16 | flex: 1; 17 | position: relative; 18 | } 19 | 20 | .graph { 21 | background-color: #fdfdfd; 22 | flex: 1; 23 | display: flex; 24 | flex-direction: column; 25 | } 26 | 27 | .graph-footer { 28 | position: absolute; 29 | bottom: 0; 30 | left: 0; 31 | right: 0; 32 | transition: all 300ms ease-in; 33 | } 34 | 35 | .labels { 36 | padding: 7px 11px; 37 | } 38 | 39 | .partial-render-info { 40 | position: absolute; 41 | padding: 2px 5px; 42 | background: transparentize(#fff, 0.7); 43 | box-shadow: #fff 0 0 18px 0; 44 | z-index: 1; 45 | color: #5d5d5d; 46 | } 47 | -------------------------------------------------------------------------------- /client/src/assets/css/Navbar.scss: -------------------------------------------------------------------------------- 1 | .Nav-url-hide { 2 | display: none !important; 3 | } 4 | 5 | .Nav-share-url { 6 | margin-left: 10px; 7 | } 8 | 9 | .Nav-pad a { 10 | padding-top: 10px !important; 11 | height: 50px; 12 | } 13 | 14 | @media only screen and (min-width: 992px) { 15 | .Nav-share-url.form-control { 16 | width: 350px; 17 | } 18 | } 19 | 20 | @media only screen and (max-width: 992px) { 21 | .Nav-share-url.form-control { 22 | width: 200px; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/src/assets/css/NodeProperties.scss: -------------------------------------------------------------------------------- 1 | .graph-overlay { 2 | position: absolute; 3 | background-color: rgba(255, 255, 255, 0.75); 4 | border-radius: 4px; 5 | border: 1px solid rgba(10, 50, 10, 0.5); 6 | right: 2px; 7 | bottom: 2px; 8 | 9 | transition: all 40ms; 10 | 11 | overflow: auto; 12 | padding: 0 4px; 13 | 14 | .title { 15 | color: #333; 16 | float: left; 17 | height: 24px; 18 | } 19 | 20 | .panel-btn { 21 | $color: #848f99; 22 | $size: 16px; 23 | 24 | display: inline-block; 25 | text-align: center; 26 | 27 | font-size: $size - 6px; 28 | height: $size; 29 | width: $size; 30 | 31 | border: 1px solid $color; 32 | border-radius: 2px; 33 | color: $color; 34 | &:hover { 35 | color: darken($color, 30); 36 | } 37 | margin-right: 2px; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/src/assets/css/PreviousQuery.scss: -------------------------------------------------------------------------------- 1 | .Previous-query { 2 | padding: 5px; 3 | } 4 | 5 | .Previous-query-pre { 6 | white-space: pre-wrap; 7 | background-color: #fdfdfc; 8 | margin: 5px 0px; 9 | word-break: break-word; 10 | } 11 | 12 | .Previous-query pre:hover { 13 | border: 1px solid gray; 14 | cursor: pointer; 15 | } 16 | 17 | .Previous-query button { 18 | visibility: hidden; 19 | } 20 | 21 | .Previous-query:hover button { 22 | visibility: visible; 23 | } 24 | 25 | .Previous-query-popover-pre { 26 | font-size: 10px; 27 | white-space: pre-wrap; 28 | } 29 | 30 | .Previous-query-text { 31 | padding: 0px 10px 0px 5px; 32 | } 33 | -------------------------------------------------------------------------------- /client/src/assets/css/Properties.scss: -------------------------------------------------------------------------------- 1 | .Properties { 2 | list-style: none; 3 | padding-left: 0; 4 | } 5 | 6 | .Properties-key-val { 7 | display: inline-block; 8 | margin-right: 21px; 9 | } 10 | 11 | // #properties { 12 | // min-height: 50px; 13 | // margin-top: 5px; 14 | // } 15 | // 16 | // .Properties { 17 | // width: 100%; 18 | // margin-top: 5px; 19 | // display: flex; 20 | // flex-wrap: wrap; 21 | // align-content: space-between; 22 | // } 23 | // 24 | // .Properties:after { 25 | // content: ""; 26 | // flex: 1 0 250px; 27 | // } 28 | // 29 | // .Properties-key-val { 30 | // flex: 1 0 250px; 31 | // padding-right: 5px; 32 | // } 33 | 34 | .Properties-key { 35 | color: #008000; 36 | font-weight: bold; 37 | display: inline-block; 38 | margin-right: 2px; 39 | } 40 | 41 | .Properties-val { 42 | color: #ba2121; 43 | } 44 | 45 | // .Properties-facets { 46 | // display: inline-block; 47 | // margin-top: 5px; 48 | // } 49 | // 50 | // @media(max-width: 850px) { 51 | // .Properties-key-val { 52 | // flex: 1 0 200px; 53 | // max-width: 200px; 54 | // } 55 | // .Properties:after { 56 | // flex: 1 0 200px; 57 | // max-width: 200px; 58 | // } 59 | // } 60 | -------------------------------------------------------------------------------- /client/src/assets/css/Response.scss: -------------------------------------------------------------------------------- 1 | .Response { 2 | width: 100%; 3 | height: 100%; 4 | padding: 5px; 5 | display: flex; 6 | flex-direction: column; 7 | } 8 | -------------------------------------------------------------------------------- /client/src/assets/css/ResponseInfo.scss: -------------------------------------------------------------------------------- 1 | .ResponseInfo { 2 | font-size: 13px; 3 | flex: 0 auto; 4 | } 5 | 6 | .ResponseInfo-partial { 7 | height: auto; 8 | } 9 | 10 | .ResponseInfo-button { 11 | float: right; 12 | margin-right: 10px; 13 | margin-left: 5px; 14 | } 15 | 16 | .ResponseInfo-stats { 17 | display: flex; 18 | } 19 | 20 | .ResponseInfo-flex { 21 | flex: 0 0 50%; 22 | } 23 | -------------------------------------------------------------------------------- /client/src/assets/css/Scratchpad.scss: -------------------------------------------------------------------------------- 1 | .Scratchpad { 2 | height: auto; 3 | min-height: 100px; 4 | width: 100%; 5 | background-color: #fffaef; 6 | margin-top: 10px; 7 | margin-left: 5px; 8 | } 9 | 10 | .Scratchpad-header { 11 | margin: 0px 5px; 12 | border-bottom: 1px solid black; 13 | padding-bottom: 5px; 14 | } 15 | 16 | .Scratchpad-heading { 17 | display: inline; 18 | padding-top: 5px; 19 | } 20 | 21 | .Scratchpad-clear { 22 | display: inline; 23 | } 24 | 25 | .Scratchpad-entries { 26 | width: 100%; 27 | margin-top: 5px; 28 | margin-left: 5px; 29 | display: flex; 30 | flex-wrap: wrap; 31 | justify-content: space-between; 32 | } 33 | 34 | .Scratchpad-entries:after { 35 | content: ""; 36 | flex: 1 0 250px; 37 | } 38 | 39 | .Scratchpad-key-val { 40 | flex: 0 0 250px; 41 | padding-right: 15px; 42 | } 43 | 44 | .Scratchpad-key { 45 | display: inline-block; 46 | font-weight: bold; 47 | } 48 | 49 | .Scratchpad-val { 50 | display: inline-block; 51 | float: right; 52 | } 53 | -------------------------------------------------------------------------------- /client/src/assets/css/SessionFooterProperties.scss: -------------------------------------------------------------------------------- 1 | .key-content { 2 | font-weight: 500; 3 | } 4 | 5 | .property-pair { 6 | margin-right: 12px; 7 | vertical-align: middle; 8 | display: inline-block; 9 | } 10 | 11 | .properties-container { 12 | padding-right: 32px; 13 | position: relative; 14 | } 15 | 16 | .property-key { 17 | margin-right: 4px; 18 | } 19 | 20 | .property-val { 21 | font-size: 12.7px; 22 | color: #6b6b6b; 23 | } 24 | 25 | .properties { 26 | text-overflow: ellipsis; 27 | white-space: nowrap; 28 | overflow: hidden; 29 | min-width: 0; 30 | 31 | &.expanded { 32 | white-space: normal; 33 | } 34 | } 35 | 36 | .label { 37 | margin-right: 6px; 38 | vertical-align: middle; 39 | } 40 | 41 | .toggle-expand-btn { 42 | position: absolute; 43 | right: 2px; 44 | top: 0; 45 | font-size: 16px; 46 | color: #6b6b6b; 47 | } 48 | -------------------------------------------------------------------------------- /client/src/assets/css/bootstrap-colors.scss: -------------------------------------------------------------------------------- 1 | // Extracted from Twitter Bootstrap's CSS 2 | // because finding the right way to import their definitions seemed hard. 3 | 4 | $blue: #007bff; 5 | $indigo: #6610f2; 6 | $purple: #6f42c1; 7 | $pink: #e83e8c; 8 | $red: #dc3545; 9 | $orange: #fd7e14; 10 | $yellow: #ffc107; 11 | $green: #28a745; 12 | $teal: #20c997; 13 | $cyan: #17a2b8; 14 | $white: #fff; 15 | $gray: #6c757d; 16 | $gray-dark: #343a40; 17 | $light: #f8f9fa; 18 | $dark: #343a40; 19 | 20 | $primary: $blue; 21 | $secondary: $gray; 22 | $success: $green; 23 | $info: $cyan; 24 | $warning: $yellow; 25 | $danger: $red; 26 | -------------------------------------------------------------------------------- /client/src/assets/images/dgraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/src/assets/images/dgraph.png -------------------------------------------------------------------------------- /client/src/assets/images/diggy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/src/assets/images/diggy.png -------------------------------------------------------------------------------- /client/src/assets/images/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/src/assets/images/graph.png -------------------------------------------------------------------------------- /client/src/assets/images/graph.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/hourglass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/load.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/loader.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/src/assets/images/tree.png -------------------------------------------------------------------------------- /client/src/components/ACL/AclPage.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .main-content.acl .acl-view { 7 | display: flex; 8 | flex: 1; 9 | flex-direction: column; 10 | padding-right: 0px; 11 | 12 | & > .vertical-panel-layout { 13 | flex: 1; 14 | } 15 | 16 | .btn-toolbar { 17 | padding: 8px 0; 18 | } 19 | 20 | .panel.first { 21 | display: flex; 22 | flex-direction: column; 23 | .datagrid { 24 | flex: 1; 25 | } 26 | } 27 | 28 | .panel.second { 29 | display: flex; 30 | flex: 1; 31 | flex-direction: column; 32 | } 33 | 34 | .panel h3.panel-title { 35 | font-size: 16px; 36 | } 37 | 38 | .details-pane-content { 39 | display: flex; 40 | flex-direction: column; 41 | flex: 1; 42 | 43 | & > * { 44 | padding-left: 8px; 45 | } 46 | 47 | .datagrid { 48 | padding-left: 0; 49 | flex: 1; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/src/components/ACL/CreateGroupModal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react' 7 | import Button from 'react-bootstrap/Button' 8 | import Form from 'react-bootstrap/Form' 9 | import Modal from 'react-bootstrap/Modal' 10 | 11 | export default function CreateGroupModal({ onCancel, onDone, createGroup }) { 12 | const [loading, setLoading] = useState(false) 13 | const [groupName, setGroupName] = useState('') 14 | const [errorMsg, setErrorMsg] = useState(null) 15 | 16 | const validate = () => { 17 | if (!groupName) { 18 | setErrorMsg('Group Name is required') 19 | return false 20 | } 21 | return true 22 | } 23 | 24 | const handleSave = async () => { 25 | if (!validate()) { 26 | return 27 | } 28 | 29 | setLoading(true) 30 | setErrorMsg(null) 31 | 32 | try { 33 | await createGroup(groupName) 34 | setLoading(false) 35 | await onDone() 36 | } catch (errorMessage) { 37 | setErrorMsg(`Could not create group: ${errorMessage}`) 38 | } 39 | } 40 | 41 | return ( 42 | 43 | 44 | Create Group 45 | 46 | 47 | 48 | Group Name 49 | 53 | setGroupName(groupName) 54 | } 55 | value={groupName} 56 | /> 57 | 58 | 59 | {!errorMsg ? null : ( 60 |
{errorMsg}
61 | )} 62 |
63 | 64 | 72 | 73 | 77 | 78 |
79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /client/src/components/AutosizeGrid.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import ReactDataGrid from 'react-data-grid' 8 | 9 | export default class AutosizeGrid extends React.Component { 10 | state = { 11 | width: 100, 12 | height: 100, 13 | } 14 | 15 | outerRef = React.createRef() 16 | dataGrid = React.createRef() 17 | 18 | componentDidMount() { 19 | const checkResize = () => { 20 | if (this.outerRef.current) { 21 | const height = this.outerRef.current.offsetHeight 22 | const width = this.outerRef.current.offsetWidth 23 | if (width !== this.state.width || height !== this.state.height) { 24 | this.setState( 25 | { 26 | height, 27 | width, 28 | }, 29 | () => this.dataGrid.current.metricsUpdated(), 30 | ) 31 | } 32 | } 33 | } 34 | setTimeout(checkResize, 1) 35 | this.resizeInterval = setInterval(checkResize, 500) 36 | } 37 | 38 | componentWillUnmount() { 39 | clearInterval(this.resizeInterval) 40 | } 41 | 42 | render() { 43 | const { className, style, ...otherProps } = this.props 44 | return ( 45 |
46 | 51 |
52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/src/components/Backups/RadioSelect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import Col from 'react-bootstrap/Col' 8 | import Form from 'react-bootstrap/Form' 9 | 10 | export default function RadioSelect({ 11 | value, 12 | onChange, 13 | radioItems, 14 | itemLabels, 15 | }) { 16 | return ( 17 | 18 | {radioItems.map((item, index) => ( 19 | 20 | onChange(item)} 26 | /> 27 | 28 | ))} 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /client/src/components/Backups/index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .backups { 7 | .backups-view { 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | 12 | .btn-toolbar { 13 | margin-bottom: 12px; 14 | } 15 | } 16 | 17 | .backups-table { 18 | flex: 1; 19 | } 20 | } 21 | 22 | .backup-well { 23 | background-color: #efefef; 24 | border-radius: 3px; 25 | margin-bottom: 8px; 26 | padding: 8px 4px; 27 | } 28 | 29 | .form-group.backup-preview { 30 | background-color: #efefef; 31 | border-radius: 3px; 32 | font-family: monospace; 33 | margin: 0; 34 | 35 | &.large { 36 | font-size: 1em; 37 | font-weight: bold; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/src/components/ConsolePage/GeoView.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @import url("https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"); 7 | 8 | .map-wrapper { 9 | background-color: white; 10 | 11 | .map { 12 | height: calc(100vh - 150px); 13 | } 14 | 15 | .options-button { 16 | position: absolute; 17 | z-index: 500; 18 | box-shadow: 0 2px 6px #0000004f; 19 | } 20 | 21 | .error-alert { 22 | position: absolute; 23 | bottom: 20px; 24 | right: 19px; 25 | z-index: 500; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/src/components/D3Graph/D3Graph.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .graph-outer { 7 | position: relative; 8 | width: 100%; 9 | 10 | canvas { 11 | position: absolute; 12 | width: 100%; 13 | height: 100%; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/components/DataExplorer/PathDisplay.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | import Breadcrumb from 'react-bootstrap/Breadcrumb' 9 | import BreadcrumbItem from 'react-bootstrap/BreadcrumbItem' 10 | 11 | export default function PathDisplay({ path, onPopState }) { 12 | function pathEl(p, index, path) { 13 | const reverseDepth = path.length - 1 - index 14 | if (p.type === 'predicate') { 15 | return ( 16 | (reverseDepth > 0 ? onPopState(reverseDepth) : null)} 20 | > 21 | 22 |   23 | {p.predicate} 24 | 25 | ) 26 | } 27 | if (p.type === 'nodeProp') { 28 | return ( 29 | (reverseDepth > 0 ? onPopState(reverseDepth) : null)} 33 | > 34 | uid:  35 | {p.uid} 36 |   37 | . 38 |   39 | {p.prop} 40 | 41 | ) 42 | } 43 | } 44 | 45 | return {path.map(pathEl)} 46 | } 47 | -------------------------------------------------------------------------------- /client/src/components/DataExplorer/index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .main-content.dataexplorer { 7 | background-color: transparent; 8 | 9 | .vertical-panel-layout { 10 | flex: 1; 11 | 12 | .panel.first { 13 | // First panel should stretch its child grid to 100% height 14 | position: relative; 15 | 16 | .grid-container { 17 | position: absolute; 18 | top: 0; 19 | bottom: 0; 20 | left: 0; 21 | right: 1px; 22 | } 23 | } 24 | } 25 | 26 | .path { 27 | padding: 0 10px; 28 | nav ol { 29 | margin-bottom: 0; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/src/components/EdgeProperties.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import Button from 'react-bootstrap/Button' 8 | import Table from 'react-bootstrap/Table' 9 | 10 | import 'assets/css/NodeProperties.scss' 11 | 12 | export default function EdgeProperties({ 13 | edge, 14 | onSelectSource, 15 | onSelectTarget, 16 | }) { 17 | if (!edge) { 18 | return null 19 | } 20 | 21 | const { facets } = edge 22 | 23 | return ( 24 |
25 | 28 |
33 | 41 | 42 | 50 |
51 | 52 | {facets && Object.keys(facets).length ? ( 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {Object.keys(facets).map((k) => ( 62 | 63 | 64 | 65 | 66 | ))} 67 | 68 |
facetvalue
{k}{String(facets[k])}
69 | ) : null} 70 |
71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /client/src/components/EntitySelector.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import classnames from 'classnames' 7 | import React from 'react' 8 | import Button from 'react-bootstrap/Button' 9 | 10 | import Label from './Label' 11 | 12 | import 'assets/css/EntitySelector.scss' 13 | 14 | export default class EntitySelector extends React.Component { 15 | state = { expanded: false } 16 | 17 | render() { 18 | const { graphLabels, onPredicateHovered } = this.props 19 | const { expanded } = this.state 20 | 21 | return ( 22 |
23 | 29 | {graphLabels.map((label) => ( 30 |
40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/components/FrameCodeTab.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import Clipboard from 'react-clipboard.js' 8 | import Highlight from 'react-highlight' 9 | 10 | import 'highlight.js/styles/atom-one-light.css' 11 | 12 | const STATE_IDLE = 0 13 | const STATE_ERROR = -1 14 | const STATE_SUCCESS = 1 15 | 16 | export default class FrameCodeTab extends React.Component { 17 | state = { 18 | copyState: STATE_IDLE, 19 | } 20 | 21 | componentWillUnmount() { 22 | this.cancelCopyTimer() 23 | } 24 | 25 | cancelCopyTimer = () => { 26 | if (this._timeout) { 27 | clearTimeout(this._timeout) 28 | } 29 | this._timeout = null 30 | } 31 | 32 | newCopyTimer = (delay) => { 33 | if (this._timeout) { 34 | clearTimeout(this._timeout) 35 | } 36 | this._timeout = setTimeout( 37 | () => 38 | this.setState({ 39 | copyState: STATE_IDLE, 40 | }), 41 | delay, 42 | ) 43 | } 44 | 45 | onCopySuccess = () => { 46 | this.setState({ 47 | copyState: STATE_SUCCESS, 48 | }) 49 | 50 | this.newCopyTimer(800) 51 | } 52 | 53 | onCopyError = () => { 54 | this.setState({ 55 | copyState: STATE_ERROR, 56 | }) 57 | 58 | this.newCopyTimer(1500) 59 | } 60 | 61 | render() { 62 | const { code } = this.props 63 | const { copyState } = this.state 64 | const json = 65 | typeof code === 'string' ? code : JSON.stringify(code, null, 2) || '' 66 | 67 | return ( 68 |
69 | json} 73 | onSuccess={this.onCopySuccess} 74 | onError={this.onCopyError} 75 | > 76 | 77 | {' '} 78 | {copyState === STATE_IDLE 79 | ? 'Copy Text to Clipboard' 80 | : copyState === STATE_SUCCESS 81 | ? 'Copied!' 82 | : 'Error Occured!'} 83 | 84 | 85 | 86 | {json && json.length > 16000 ? ( 87 |
{json}
88 | ) : ( 89 | {json} 90 | )} 91 |
92 | ) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/FrameBodyToolbar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import Tab from 'react-bootstrap/Tab' 8 | import Tabs from 'react-bootstrap/Tabs' 9 | 10 | import { TAB_GEO, TAB_JSON, TAB_QUERY, TAB_VISUAL } from 'actions/frames' 11 | import GraphIcon from 'components/GraphIcon' 12 | 13 | export default function FrameBodyToolbar({ 14 | frame, 15 | activeTab, 16 | setActiveTab, 17 | tabResult, 18 | }) { 19 | const isQueryFrame = frame.action === 'query' 20 | const isError = 21 | tabResult.error || (tabResult.response && tabResult.response.error) 22 | 23 | const toolbarBtn = (id, icon, label) => ( 24 | 28 |
{icon}
29 | {label} 30 | 31 | } 32 | /> 33 | ) 34 | 35 | const visualTab = () => { 36 | if (isQueryFrame && !isError) { 37 | return toolbarBtn(TAB_VISUAL, , 'Graph') 38 | } 39 | if (isError) { 40 | return toolbarBtn( 41 | TAB_VISUAL, 42 | , 43 | 'Error', 44 | ) 45 | } 46 | return toolbarBtn( 47 | TAB_VISUAL, 48 | , 49 | 'Message', 50 | ) 51 | } 52 | 53 | return ( 54 | 60 | {visualTab()} 61 | {toolbarBtn(TAB_JSON, , 'JSON')} 62 | {toolbarBtn(TAB_QUERY, , 'Request')} 63 | {toolbarBtn(TAB_GEO, , 'Geo')} 64 | 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/FrameErrorMessage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default function FrameErrorMessage({ error }) { 9 | return ( 10 | 11 |
12 |

13 | Error Name: {error.name} 14 |

15 |

16 | Message: {error.errors?.[0]?.message} 17 |

18 |
19 |

20 | Raw Error: 21 |

22 |
{JSON.stringify(error, null, 2)}
23 |
24 | 25 |
26 | {' '} 27 | Error occurred 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/FrameHistoric.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default function FrameLoading() { 9 | return ( 10 |
11 |
12 |
13 | This frame from history has been executed already. 14 |
15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/FrameMessage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import FrameErrorMessage from './FrameErrorMessage' 8 | 9 | export default function FrameMessage({ frame, tabResult }) { 10 | if (tabResult.error) { 11 | return 12 | } 13 | 14 | return ( 15 |
16 | {`Message: ${ 17 | tabResult.response && 18 | tabResult.response.data && 19 | tabResult.response.data.message 20 | }`} 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/QueryPreview.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | // CollapseQuery replaces deeply nested blocks in a query with ellipsis. 9 | export function collapseQuery(query) { 10 | const depthLimit = 3 11 | let ret = '' 12 | let depth = 0 13 | 14 | for (let i = 0; i < query.length; i++) { 15 | const char = query[i] 16 | 17 | if (char === '{') { 18 | depth++ 19 | 20 | if (depth === depthLimit) { 21 | ret += char 22 | ret += ' ... ' 23 | continue 24 | } 25 | } else if (char === '}') { 26 | depth-- 27 | } 28 | 29 | if (depth >= depthLimit) { 30 | continue 31 | } 32 | 33 | ret += char 34 | } 35 | 36 | return ret 37 | } 38 | 39 | export default function QueryPreview({ 40 | frameId, 41 | action, 42 | hasError, 43 | onClick, 44 | query, 45 | }) { 46 | return ( 47 |
48 | 55 | {!hasError ? null : ( 56 | 57 | 58 | 59 | 60 | )}{' '} 61 | {collapseQuery(query)} 62 |
63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/SessionFooter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | import SessionFooterProperties from './SessionFooterProperties' 9 | import SessionFooterResult from './SessionFooterResult' 10 | 11 | // TODO: this component isn't used at the moment. Maybe delete. 12 | 13 | export default function SessionFooter({ 14 | response, 15 | currentTab, 16 | hoveredNode, 17 | selectedNode, 18 | }) { 19 | return ( 20 |
21 | {selectedNode || hoveredNode ? ( 22 | 23 | ) : ( 24 | 25 | )} 26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/SessionFooterResult.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import pluralize from 'pluralize' 7 | import React from 'react' 8 | 9 | export default function SessionFooterResult({ currentTab, response }) { 10 | const currentAction = currentTab === 'graph' ? 'Showing' : 'Found' 11 | 12 | return ( 13 |
14 |
15 | 16 | {currentAction} {response.nodes.length}{' '} 17 | {pluralize('node', response.nodes.length)} and{' '} 18 | {response.edges.length}{' '} 19 | {pluralize('edge', response.edges.length)} 20 | 21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /client/src/components/FrameLayout/SharingSettings.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .sharingSettings { 7 | background-color: #eee; 8 | margin: -8px 0; 9 | padding: 8px; 10 | 11 | .urlRow { 12 | border: red; 13 | display: flex; 14 | flex-wrap: nowrap; 15 | flex-direction: row; 16 | min-width: 240px; 17 | 18 | .btnCopy { 19 | background-color: #fff; 20 | border-radius: 4px; 21 | border: 1px solid var(--primary); 22 | margin-left: 2px; 23 | path { 24 | fill: var(--primary); 25 | } 26 | } 27 | } 28 | 29 | .checkIncludeServerAddr { 30 | margin-left: 20px; 31 | } 32 | 33 | .form-group { 34 | padding-bottom: 4px; 35 | margin-bottom: 6px; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/src/components/FrameList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | import FrameItem from './FrameItem' 9 | 10 | import '../assets/css/Frames.scss' 11 | 12 | export default class FrameList extends React.Component { 13 | state = { 14 | count: 10, 15 | } 16 | 17 | loadMore = () => { 18 | this.setState((state) => ({ 19 | count: state.count + 10, 20 | })) 21 | } 22 | 23 | render() { 24 | const { activeFrameId, frames } = this.props 25 | const { count } = this.state 26 | 27 | const finalFrames = frames.slice(0, count) 28 | const loadMoreButton = frames.length > count && ( 29 | 36 | ) 37 | 38 | return ( 39 |
40 | {finalFrames.map((frame) => ( 41 | 47 | ))} 48 | {loadMoreButton} 49 |
50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/src/components/FrameLoading.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | import loader from '../assets/images/loader.svg' 9 | 10 | export default function FrameLoading() { 11 | return ( 12 |
13 |
14 | Loading Indicator 15 |
Fetching result...
16 |
17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /client/src/components/GraphIcon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default function GraphIcon() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 19 | 24 | 29 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /client/src/components/HealthDot/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | import { FetchError, Fetching, OK, Unknown } from 'lib/constants' 9 | import { isLatestVersion } from 'lib/dgraph-syntax' 10 | 11 | import './index.scss' 12 | 13 | export default function HealthDot({ health, version }) { 14 | const getHealthClass = (health) => { 15 | if (health === OK) { 16 | return 'ok' 17 | } 18 | if (health === Unknown || health === Fetching) { 19 | return 'fetching' 20 | } 21 | if (health === FetchError) { 22 | return 'error' 23 | } 24 | } 25 | const healthClass = getHealthClass(health) 26 | const needsUpdate = version && !isLatestVersion(version) 27 | const versionClass = needsUpdate && healthClass === 'ok' ? 'outdated' : '' 28 | 29 | function getTitle() { 30 | switch (healthClass) { 31 | case 'ok': 32 | return needsUpdate ? `Healthy, Dgraph Upgrade Available` : 'Healthy' 33 | case 'fetching': 34 | return 'Connecting Now' 35 | 36 | case 'error': 37 | return 'Connection Error' 38 | default: 39 | return 'Unknown Status' 40 | } 41 | } 42 | 43 | return ( 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /client/src/components/HealthDot/index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .health-dot { 7 | display: inline-block; 8 | font-size: 0.75em; 9 | margin-top: 0.375em; 10 | position: relative; 11 | 12 | &.outdated i { 13 | color: var(--warning) !important; 14 | } 15 | 16 | i { 17 | color: var(--dark); 18 | opacity: 0; 19 | transition: 20 | opacity 250ms, 21 | color 750ms; 22 | } 23 | 24 | i:nth-child(n + 2) { 25 | position: absolute; 26 | display: inline-block; 27 | left: 0; 28 | top: 0; 29 | } 30 | 31 | &.ok { 32 | i { 33 | opacity: 0; 34 | } 35 | i.circle { 36 | opacity: 1; 37 | color: var(--success); 38 | } 39 | } 40 | 41 | &.unknown { 42 | i { 43 | opacity: 0; 44 | } 45 | i.circle { 46 | opacity: 1; 47 | } 48 | } 49 | 50 | &.error { 51 | i { 52 | opacity: 0; 53 | } 54 | i.error { 55 | opacity: 1; 56 | animation: error-glow 3s infinite; 57 | } 58 | 59 | @keyframes error-glow { 60 | 0% { 61 | color: var(--secondary); 62 | } 63 | 25% { 64 | color: var(--danger); 65 | } 66 | 50% { 67 | color: var(--secondary); 68 | } 69 | } 70 | } 71 | 72 | &.fetching { 73 | i { 74 | opacity: 0; 75 | } 76 | i.fetching { 77 | color: var(--primary); 78 | opacity: 1; 79 | @keyframes plug { 80 | 0% { 81 | transform: translateY(0px); 82 | } 83 | 25% { 84 | transform: translateY(2px); 85 | } 86 | 50% { 87 | transform: translateY(0px); 88 | } 89 | } 90 | animation: plug 3s infinite; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /client/src/components/Label.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | function getTextColor(bgColor) { 9 | const nThreshold = 105 10 | const components = getRGBComponents(bgColor) 11 | const bgDelta = 12 | components.R * 0.299 + components.G * 0.587 + components.B * 0.114 13 | 14 | return 255 - bgDelta < nThreshold ? '#000000' : '#ffffff' 15 | } 16 | 17 | function getRGBComponents(color) { 18 | const r = color.substring(1, 3) 19 | const g = color.substring(3, 5) 20 | const b = color.substring(5, 7) 21 | 22 | return { 23 | R: parseInt(r, 16), 24 | G: parseInt(g, 16), 25 | B: parseInt(b, 16), 26 | } 27 | } 28 | 29 | export default ({ color, pred, label, ...domProps }) => ( 30 |
38 | {pred} 39 |
40 | ) 41 | -------------------------------------------------------------------------------- /client/src/components/LicenseWarning/LicenseWarning.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .license-warning { 7 | background-color: rgba(240, 185, 141, 0.9); 8 | border-radius: 4px; 9 | box-shadow: 0 0 2px 3px rgba(0, 0, 0, 0.25); 10 | left: 20vw; 11 | padding: 8px; 12 | position: absolute; 13 | right: 20vw; 14 | top: 10px; 15 | z-index: 10; 16 | 17 | &.expired { 18 | background-color: rgba(220, 53, 69, 0.9); 19 | a { 20 | color: rgb(0, 0, 238); 21 | } 22 | } 23 | 24 | .dismiss { 25 | background: transparent; 26 | border: none; 27 | float: right; 28 | font-weight: bold; 29 | font-size: 16px; 30 | 31 | &:hover, 32 | &:focus { 33 | text-decoration: underline; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/src/components/NodeProperties.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import Button from 'react-bootstrap/Button' 8 | import Table from 'react-bootstrap/Table' 9 | 10 | import '../assets/css/NodeProperties.scss' 11 | 12 | export default function NodeProperties({ node, onCollapseNode, onExpandNode }) { 13 | if (!node) { 14 | return null 15 | } 16 | 17 | const { attrs, facets } = node.properties 18 | 19 | return ( 20 |
21 | 22 |
27 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {attrs 48 | ? Object.keys(attrs).map((k) => ( 49 | 50 | 51 | 52 | 53 | )) 54 | : null} 55 | 56 |
pred.value
{k}{JSON.stringify(attrs[k])}
57 | 58 | {facets && Object.keys(facets).length ? ( 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {Object.keys(facets).map((k) => ( 68 | 69 | 70 | 71 | 72 | ))} 73 | 74 |
facetvalue
{k}{String(facets[k])}
75 | ) : null} 76 |
77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /client/src/components/PanelLayout/HorizontalPanelLayout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default class HorizontalPanelLayout extends React.Component { 9 | state = { 10 | width: -1, 11 | height: -1, 12 | } 13 | 14 | body = React.createRef() 15 | second = React.createRef() 16 | 17 | componentDidMount() { 18 | window.addEventListener('resize', this._onResize) 19 | } 20 | 21 | componentDidUpdate() { 22 | this._onResize() 23 | } 24 | 25 | _onResize = () => { 26 | const { offsetWidth, offsetHeight } = this.body.current 27 | // Only setState when dimensions actually changed to avoid infinite loop 28 | if ( 29 | offsetWidth !== this.state.width || 30 | offsetHeight !== this.state.height 31 | ) { 32 | setTimeout( 33 | () => 34 | this.setState({ 35 | height: offsetHeight, 36 | width: offsetWidth, 37 | }), 38 | 0, 39 | ) 40 | } 41 | } 42 | 43 | componentWillUnmount() { 44 | window.removeEventListener('resize', this._onResize) 45 | } 46 | 47 | render() { 48 | const { first, second } = this.props 49 | 50 | return ( 51 |
52 |
{first}
53 | 54 |
55 | {second} 56 |
57 |
58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /client/src/components/PanelLayout/PanelLayout.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | $separator-fullwidth: 8px; 7 | $separator-margin: 4px; 8 | 9 | .panel-layout { 10 | display: flex; 11 | flex: 1; 12 | flex-direction: column; 13 | max-width: 100%; 14 | overflow-x: hidden; 15 | position: relative; 16 | 17 | .panel { 18 | background-color: transparent; 19 | box-sizing: border-box; 20 | margin-bottom: 0; 21 | } 22 | 23 | .toolbar { 24 | position: absolute; 25 | height: 40px; 26 | right: 4px; 27 | top: 20px; 28 | 29 | button { 30 | background-color: transparent; 31 | &.active { 32 | background-color: #fff; 33 | } 34 | font-size: 16px; 35 | line-height: 30px; 36 | border-radius: 4px; 37 | height: 30px; 38 | width: 30px; 39 | margin-right: 5px; 40 | padding: 0; 41 | text-align: center; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/src/components/PanelLayout/VerticalPanelLayout.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | $separator-fullwidth: 8px; 7 | $separator-margin: 4px; 8 | 9 | .vertical-panel-layout { 10 | display: flex; 11 | flex-direction: row; 12 | position: relative; 13 | 14 | .separator { 15 | position: absolute; 16 | background-color: #eee; 17 | border: 1px solid #ccc; 18 | border-radius: 2px; 19 | cursor: col-resize; 20 | height: 100%; 21 | 22 | box-sizing: border-box; 23 | width: $separator-fullwidth; 24 | } 25 | 26 | .panel { 27 | background-color: transparent; 28 | margin-bottom: 0; 29 | 30 | &.first { 31 | margin-right: $separator-margin; 32 | } 33 | &.second { 34 | // Second panel flex-grows horizontally to occupy remaining width 35 | flex: 1; 36 | margin-left: $separator-margin; 37 | overflow-y: auto; 38 | max-height: 100%; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/src/components/PanelLayout/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import classnames from 'classnames' 7 | import React from 'react' 8 | 9 | import HorizontalPanelLayout from './HorizontalPanelLayout' 10 | import VerticalPanelLayout from './VerticalPanelLayout' 11 | 12 | import './PanelLayout.scss' 13 | 14 | export default class PanelLayout extends React.Component { 15 | constructor(props) { 16 | super(props) 17 | 18 | this.state = { 19 | isVertical: !!this.props.disableHorizontal, 20 | } 21 | this.onSetVertical(this.state.isVertical) 22 | 23 | this.actualPanels = React.createRef() 24 | } 25 | 26 | onSetVertical = (isVertical) => 27 | this.props.onSetVertical && this.props.onSetVertical(isVertical) 28 | 29 | setVertical = (isVertical) => { 30 | this.setState({ isVertical }) 31 | this.onSetVertical(isVertical) 32 | } 33 | 34 | render() { 35 | const { disableHorizontal, first, second, title } = this.props 36 | const { isVertical } = this.state 37 | 38 | return ( 39 |
45 | {!title ? null :

{title}

} 46 | {disableHorizontal ? null : ( 47 |
48 | 54 | 60 |
61 | )} 62 | {isVertical ? ( 63 | 64 | ) : ( 65 | 66 | )} 67 |
68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /client/src/components/PartialRenderInfo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default function PartialRenderInfo({ remainingNodes, onShowMoreNodes }) { 9 | return ( 10 |
11 | Only a subset of the graph was rendered. 12 | 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /client/src/components/PredicateSearchBar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default function PredicateSearchBar({ predicates, onFilter }) { 9 | /* 10 | * Called when the search bar change event fires. Filters predicates and fires the 'filter' event. 11 | * @event - input change event 12 | */ 13 | const handleChange = (event) => { 14 | const searchValue = event.target.value 15 | 16 | const filteredPredicates = predicates.filter((p) => 17 | p.predicate.toLowerCase().includes(searchValue.toLowerCase()), 18 | ) 19 | 20 | onFilter(filteredPredicates) 21 | } 22 | 23 | // Render 24 | return ( 25 |
26 | 31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /client/src/components/Progress.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import ProgressBar from 'react-bootstrap/ProgressBar' 8 | 9 | import '../assets/css/Graph.scss' 10 | 11 | export default function Progress({ perc }) { 12 | return ( 13 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /client/src/components/Properties.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | // import "../assets/css/Properties.scss"; 9 | 10 | export default class Properties extends React.Component { 11 | render() { 12 | const { entity } = this.props 13 | const nodeProperties = JSON.parse(entity.title) 14 | 15 | // Nodes have facets and attrs keys. 16 | const isEdge = Object.keys(nodeProperties).length === 1 17 | const attrs = nodeProperties.attrs || {} 18 | const facets = nodeProperties.facets || {} 19 | 20 | return ( 21 |
22 | Showing {isEdge ? 'edge' : 'node'}: 23 | {!isEdge && ( 24 |
25 |
    26 | {Object.keys(attrs).map((key, idx) => { 27 | return ( 28 |
  • 29 | {key}: 30 | {String(attrs[key])} 31 |
  • 32 | ) 33 | })} 34 |
35 |
36 | )} 37 | {Object.keys(facets).length > 0 && !isEdge && ( 38 | Facets 39 | )} 40 |
    41 | {Object.keys(facets).map((key, idx) => { 42 | return ( 43 |
  • 44 | {key}: 45 | {String(facets[key])} 46 |
  • 47 | ) 48 | })} 49 |
50 |
51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/src/components/QueryVarsEditor/index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .query-vars-editor { 7 | background-color: #fff; 8 | border-top: 1px solid #ccc; 9 | position: relative; 10 | z-index: 100; 11 | 12 | max-height: 142px; 13 | overflow-x: hidden; 14 | overflow-y: scroll; 15 | 16 | .btn { 17 | background-color: #fff; 18 | border: 1px solid var(--secondary); 19 | border-radius: 2px; 20 | margin: 4px; 21 | overflow: hidden; 22 | padding: 0 2px; 23 | text-align: center; 24 | $size: 24px; 25 | height: $size; 26 | font-size: 12px; 27 | line-height: $size; 28 | } 29 | 30 | .btn-drop-all { 31 | border: 1px solid transparent; 32 | color: var(--danger); 33 | float: right; 34 | opacity: 0; 35 | transition: all 200ms; 36 | } 37 | &:hover .btn-drop-all { 38 | opacity: 0.5; 39 | &:hover { 40 | opacity: 1; 41 | border: 1px solid var(--danger); 42 | } 43 | } 44 | 45 | .count { 46 | color: var(--secondary); 47 | font-size: 13px; 48 | } 49 | 50 | .vars { 51 | flex: 1; 52 | flex-direction: column; 53 | border-top: 1px solid var(--light); 54 | 55 | .var { 56 | display: flex; 57 | flex-direction: row; 58 | position: relative; 59 | 60 | &:hover { 61 | background-color: var(--light); 62 | .delete { 63 | opacity: 1; 64 | } 65 | } 66 | 67 | .delete { 68 | border: none; 69 | background: none; 70 | color: var(--secondary); 71 | margin-top: 2px; 72 | opacity: 0; 73 | transition: opacity 50ms; 74 | position: absolute; 75 | right: 2px; 76 | } 77 | 78 | .controls { 79 | width: 30px; 80 | display: inline-block; 81 | border-right: 1px solid var(--light); 82 | text-align: center; 83 | 84 | .checkbox-send { 85 | display: inline-block; 86 | vertical-align: text-bottom; 87 | } 88 | } 89 | 90 | .content { 91 | flex: 1; 92 | margin: 0 4px; 93 | 94 | .edit-var { 95 | width: 100%; 96 | margin: 2px; 97 | margin-left: 0; 98 | font-family: monospace; 99 | font-size: 13px; 100 | } 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /client/src/components/QueryView/QueryView.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .query-view { 7 | display: flex; 8 | flex: 1; 9 | flex-direction: column; 10 | padding-right: 0px; 11 | 12 | .vertical-panel-layout { 13 | // Grow panel-layout all the way to the bottom of the screen. 14 | flex-grow: 1; 15 | 16 | .panel.first { 17 | // Left panel is editor-panel. Grow it 18 | display: flex; 19 | flex-direction: column; 20 | 21 | position: relative; 22 | 23 | .query-view-left-scrollable { 24 | position: absolute; 25 | bottom: 0; 26 | left: 0; 27 | right: 0; 28 | top: 0; 29 | 30 | overflow-x: hidden; 31 | overflow-y: auto; 32 | 33 | // box-shadow: inset green 0 0 3px 0; 34 | 35 | .editor-panel { 36 | margin-bottom: 10px; 37 | height: calc(100vh - 170px); 38 | } 39 | 40 | .frame-list-outer { 41 | text-align: center; 42 | * { 43 | text-align: left; 44 | } 45 | padding: 0 2px; 46 | 47 | li { 48 | margin-bottom: 14px; 49 | } 50 | } 51 | 52 | .btn-load-more { 53 | margin-bottom: 10px; 54 | } 55 | 56 | .history-label { 57 | margin-bottom: 5px; 58 | position: relative; 59 | left: 50%; 60 | transform: translateX(-50%); 61 | text-transform: uppercase; 62 | opacity: 0.5; 63 | 64 | i.fa-chevron-down { 65 | font-size: 0.75em; 66 | } 67 | } 68 | } 69 | } 70 | 71 | .panel.second { 72 | // Right panel is single full page FrameItem. 73 | display: flex; 74 | flex-direction: column; 75 | overflow: hidden; 76 | 77 | .frame-item { 78 | flex: 1; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /client/src/components/QueryView/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import { useSelector } from 'react-redux' 8 | 9 | import { TAB_JSON } from 'actions/frames' 10 | import EditorPanel from '../EditorPanel' 11 | import FrameItem from '../FrameItem' 12 | import FrameList from '../FrameList' 13 | import VerticalPanelLayout from '../PanelLayout/VerticalPanelLayout' 14 | 15 | import './QueryView.scss' 16 | 17 | export default function QueryView() { 18 | const { 19 | activeFrameId, 20 | tab: activeTab, 21 | frameResults, 22 | items: frames, 23 | } = useSelector((store) => store.frames) 24 | 25 | const frame = frames.find((f) => f.id === activeFrameId) || frames[0] || {} 26 | const tabName = 27 | frame.action === 'mutate' || activeTab === 'geo' || activeTab === 'timeline' 28 | ? TAB_JSON 29 | : activeTab 30 | const tabResult = 31 | frame && frameResults[frame.id] && frameResults[frame.id][tabName] 32 | 33 | return ( 34 |
35 |

Console

36 | 39 | 40 | 41 | 42 | {' '} 46 | History{' '} 47 | 51 | 52 | 53 |
54 | } 55 | second={ 56 | frames.length ? ( 57 | 65 | ) : ( 66 |
67 | Please run a query or a mutation 68 |
69 | ) 70 | } 71 | /> 72 | 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /client/src/components/ServerConnectionModal.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .modal.server-connection .modal-content { 7 | .modal-header { 8 | overflow: hidden; 9 | position: relative; 10 | } 11 | span.title { 12 | margin-left: 38px; 13 | } 14 | img.logo { 15 | width: 70px; 16 | position: absolute; 17 | left: -26px; 18 | top: 15px; 19 | } 20 | } 21 | 22 | .modal.server-connection .modal-footer { 23 | justify-content: space-between; 24 | } 25 | .modal.server-connection .modal-body { 26 | overflow: hidden; 27 | 28 | .main-row { 29 | min-height: 60vh; 30 | } 31 | 32 | .url-input-box { 33 | background-color: #eee; 34 | margin: -16px -31px 10px -15px; 35 | padding: 12px 46px 15px 16px; 36 | } 37 | 38 | .settings { 39 | padding-right: 15px; 40 | .tab-content { 41 | padding: 8px 8px 0; 42 | } 43 | } 44 | 45 | .col-history { 46 | box-shadow: 1px 0 #eee; 47 | 48 | .list-group-item { 49 | cursor: pointer; 50 | overflow: hidden; 51 | position: relative; 52 | 53 | .btn-connect { 54 | display: none; 55 | font-size: 0.75rem; 56 | padding: 0.25rem; 57 | position: absolute; 58 | right: 4px; 59 | top: 50%; 60 | transform: translateY(-50%); 61 | } 62 | &:hover .btn-connect { 63 | display: block; 64 | } 65 | &.active .btn-connect { 66 | background: none; 67 | border: 0; 68 | } 69 | 70 | p { 71 | line-break: anywhere; 72 | margin: 0; 73 | max-height: 24px; 74 | overflow: hidden; 75 | 76 | &.minor { 77 | font-size: 0.75em; 78 | opacity: 0.9; 79 | 80 | button.removeUrl { 81 | border: none; 82 | padding: 0; 83 | margin: 0; 84 | background: transparent; 85 | opacity: 0; 86 | width: 0; 87 | transition: all 100ms; 88 | 89 | &:focus, 90 | &:active { 91 | opacity: 1; 92 | width: auto; 93 | } 94 | } 95 | } 96 | } 97 | &:hover p.minor button.removeUrl { 98 | opacity: 1; 99 | width: auto; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /client/src/components/SessionList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | import CSSTransition from 'react-transition-group/CSSTransition' 8 | import TransitionGroup from 'react-transition-group/TransitionGroup' 9 | 10 | import SessionItem from './SessionItem' 11 | 12 | import '../assets/css/SessionList.scss' 13 | 14 | export default function SessionList({ sessions }) { 15 | return ( 16 |
    17 | 18 | {sessions.map((session) => { 19 | return ( 20 | 25 | 26 | 27 | ) 28 | })} 29 | 30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /client/src/components/SidebarInfo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default function SidebarInfo() { 9 | return ( 10 |
11 |
12 |

Dgraph Ratel

13 | 14 |

An interface to easily query and visualize your data

15 | 27 | 39 |
40 | 41 |
42 |

Dgraph

43 | 44 |

Fast, distributed graph database

45 | 46 | 78 |
79 | 80 |

81 | Built at {process.env.RATEL_BUILT_AT} 82 |
83 | Commit: {process.env.RATEL_COMMIT_ID} 84 |
85 | Commit Info: {process.env.RATEL_COMMIT_INFO} 86 |
87 |

88 |
89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /client/src/components/TreeIcon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | export default function TreeIcon({ active }) { 9 | return ( 10 | 18 | 19 | 33 | 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /client/src/components/WizardSteps/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | import './index.scss' 9 | 10 | export default function WizardSteps({ steps = [] }) { 11 | return ( 12 |
    13 | {steps.map(({ title, content, className }, idx) => ( 14 | 15 |
  1. 16 | {content} 17 |
  2. 18 |
  3. 19 | 20 | ))} 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /client/src/components/WizardSteps/index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .wizard-steps { 7 | display: flex; 8 | flex-direction: row; 9 | justify-content: space-between; 10 | 11 | $size: 24px; 12 | $lineWidth: 2px; 13 | 14 | height: $lineWidth; 15 | 16 | margin: ($size / 2 + 8px) 0; 17 | padding: 0; 18 | position: relative; 19 | 20 | li.sep { 21 | list-style: none; 22 | height: $lineWidth; 23 | margin: 0 2px; 24 | flex: 1; 25 | 26 | &:last-child { 27 | width: 0; 28 | display: none; 29 | } 30 | } 31 | 32 | a.wizard-link { 33 | color: var(--light); 34 | } 35 | 36 | li.item { 37 | display: block; 38 | margin: 0; 39 | text-align: center; 40 | transform: translateY(-$size / 2 + $lineWidth / 2); 41 | list-style: none; 42 | line-height: $size; 43 | 44 | width: $size; 45 | height: $size; 46 | color: var(--light); 47 | border-radius: $size / 2; 48 | font-size: $size / 2; 49 | box-shadow: 0 0 1px 3px #eee; 50 | } 51 | 52 | li { 53 | background-color: var(--secondary); 54 | } 55 | 56 | li.ok { 57 | background-color: var(--success); 58 | } 59 | 60 | li.warning { 61 | background-color: var(--warning); 62 | } 63 | li.error { 64 | background-color: var(--danger); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/src/components/ZeroUrlWidget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react' 7 | import Form from 'react-bootstrap/Form' 8 | import { useDispatch, useSelector } from 'react-redux' 9 | 10 | import { updateZeroUrl } from 'actions/connection' 11 | 12 | export default function ZeroUrlWidget() { 13 | const currentServer = useSelector( 14 | (state) => state.connection.serverHistory[0], 15 | ) 16 | const dispatch = useDispatch() 17 | 18 | const [zeroUrl, setZeroUrl] = useState(currentServer.zeroUrl) 19 | 20 | return ( 21 |
e.preventDefault()}> 22 | 23 | Dgraph Zero URL: 24 | { 29 | dispatch(updateZeroUrl(zeroUrl)) 30 | setZeroUrl(e.target.value) 31 | }} 32 | style={{ 33 | width: '100%', 34 | }} 35 | /> 36 | 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /client/src/components/schema/PredicateTabs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react' 7 | 8 | import Tab from 'react-bootstrap/Tab' 9 | import Tabs from 'react-bootstrap/Tabs' 10 | 11 | import PredicatePropertiesPanel from './PredicatePropertiesPanel' 12 | import SampleDataPanel from './SampleDataPanel' 13 | 14 | export default function PredicateTabs({ 15 | executeQuery, 16 | onAfterDrop, 17 | onAfterUpdate, 18 | onOpenGeneratedQuery, 19 | predicate, 20 | }) { 21 | const [rightPaneTab, setRightPaneTab] = useState('props') // props or "samples" 22 | return ( 23 | 29 | 30 | {!predicate ? null : ( 31 | 38 | )} 39 | 40 | 45 | {!predicate ? null : ( 46 | 52 | )} 53 | 54 | 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /client/src/components/schema/SampleDataPanel/SamplesTable.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import Adapter from '@wojtekmaj/enzyme-adapter-react-17' 7 | import Enzyme from 'enzyme' 8 | import { mount } from 'enzyme' 9 | import React from 'react' 10 | 11 | import SamplesTable from './SamplesTable' 12 | 13 | Enzyme.configure({ adapter: new Adapter() }) 14 | 15 | test("SamplesTable shouldn't crash on scalars or nested objects", () => { 16 | const samples = [ 17 | { 18 | uid: 333, 19 | stringVal: 's', 20 | intVal: 100, 21 | loc: { type: 'Point', coords: [1, 2, 3] }, 22 | arr: [{ uid: 1 }, { uid: 2 }], 23 | }, 24 | ] 25 | const wrapper = mount( 26 | , 37 | ) 38 | wrapper.setState({ samples }) 39 | // 4 properties - string, int, loc, arr 40 | expect(wrapper.find('tr').length).toEqual(4) 41 | }) 42 | -------------------------------------------------------------------------------- /client/src/components/schema/SampleDataPanel/index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .when-hovered { 7 | opacity: 0; 8 | transition: opacity 100ms; 9 | } 10 | 11 | .with-hover-btn { 12 | &:focus, 13 | &:hover { 14 | .when-hovered { 15 | opacity: 1; 16 | } 17 | } 18 | .btn { 19 | margin: -2px 0; 20 | .icon-container { 21 | display: inline-block; 22 | width: 12px; 23 | height: 12px; 24 | padding-top: 2px; 25 | * { 26 | fill: #fff; 27 | } 28 | } 29 | } 30 | } 31 | 32 | .samples-panel { 33 | background-color: #f8f9fa; 34 | } 35 | 36 | .sample-cards { 37 | padding: 10px; 38 | background-color: #f8f9fa; 39 | 40 | .card { 41 | margin-bottom: 1rem; 42 | font-size: 1rem; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/src/components/schema/Schema.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | .main-content.schema { 7 | background-color: transparent; 8 | } 9 | 10 | .schema-view { 11 | display: flex; 12 | flex: 1; 13 | flex-direction: column; 14 | padding-right: 0px; 15 | position: relative; 16 | 17 | .schema-toolbar { 18 | margin-bottom: 16px; 19 | 20 | button.btn-discouraged:hover { 21 | text-decoration: dashed underline #999; 22 | } 23 | } 24 | 25 | .vertical-panel-layout { 26 | // Grow panel-layout all the way to the bottom of the screen. 27 | flex-grow: 1; 28 | 29 | .panel.first { 30 | // Left panel is schema grid. Flex-stretch it to entire vertical space 31 | display: flex; 32 | flex-direction: column; 33 | .grid-container { 34 | flex: 1; 35 | } 36 | } 37 | } 38 | 39 | .tab-content { 40 | flex: 1; 41 | } 42 | 43 | .panel.second { 44 | display: flex; 45 | flex-direction: column; 46 | 47 | .tab-content { 48 | // Create a context for "height: 100%" for .nostretch children below. 49 | position: relative; 50 | } 51 | .nostretch { 52 | position: absolute; 53 | top: 0; 54 | left: 0; 55 | right: 0; 56 | bottom: 0; 57 | // height: 100%; 58 | overflow-y: auto; 59 | 60 | table { 61 | width: 100%; 62 | } 63 | } 64 | } 65 | 66 | .grid-container { 67 | flex: 1; 68 | overflow: hidden; 69 | } 70 | 71 | .schema-badges { 72 | float: right; 73 | margin-top: -1px; 74 | div { 75 | height: 14px; 76 | width: 14px; 77 | background-color: #f0ad4e; 78 | color: #333; 79 | border-radius: 2px; 80 | display: inline-block; 81 | margin-right: 2px; 82 | line-height: 14px; 83 | font-size: 10px; 84 | cursor: default; 85 | text-align: center; 86 | } 87 | } 88 | 89 | .panel.second { 90 | .datagrid { 91 | flex: 1; 92 | } 93 | 94 | .type-properties { 95 | margin: 0 8px; 96 | display: flex; 97 | flex: 1; 98 | flex-direction: column; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /client/src/components/schema/SchemaRawModeModal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react' 7 | import Button from 'react-bootstrap/Button' 8 | import Modal from 'react-bootstrap/Modal' 9 | 10 | import Editor from 'containers/Editor' 11 | import { getRawSchema } from 'lib/dgraph-syntax' 12 | 13 | export default function SchemaRawModeModal({ 14 | executeQuery, 15 | onAfterUpdate, 16 | onCancel, 17 | onDropData, 18 | schema, 19 | types, 20 | }) { 21 | const [value, setValue] = useState(getRawSchema(schema, types)) 22 | const [editorKey, setEditorKey] = useState(1) 23 | const [errorMsg, setErrorMsg] = useState(null) 24 | const [updating, setUpdating] = useState(false) 25 | 26 | const handleUpdate = async () => { 27 | setUpdating(true) 28 | setErrorMsg(null) 29 | 30 | try { 31 | await executeQuery(value + '\n', 'alter', true) 32 | onAfterUpdate() 33 | } catch (error) { 34 | setErrorMsg(`Could not alter schema: ${error?.message}`) 35 | } finally { 36 | setUpdating(false) 37 | } 38 | } 39 | 40 | const handleResetClick = () => { 41 | setValue(getRawSchema(schema, types)) 42 | setEditorKey(editorKey + 1) 43 | setErrorMsg(null) 44 | } 45 | 46 | return ( 47 | 48 | 49 | Edit Schema File 50 | 51 | 52 |
53 | setValue(value)} 58 | /> 59 |
60 | {!errorMsg ? null : ( 61 |
{errorMsg}
62 | )} 63 |
64 | 65 | 68 | 71 | 72 | 75 | 76 | 79 | 80 |
81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /client/src/components/schema/TypeProperties.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react' 7 | 8 | import AutosizeGrid from 'components/AutosizeGrid' 9 | 10 | export default function TypeProperties({ 11 | executeQuery, 12 | onAfterUpdate, 13 | onEdit, 14 | type, 15 | }) { 16 | const fields = type.fields.slice().sort((a, b) => (a.name < b.name ? -1 : 1)) 17 | 18 | const columns = [ 19 | { 20 | key: 'name', 21 | name: 'Name', 22 | resizable: true, 23 | }, 24 | { 25 | key: 'type', 26 | name: 'Type', 27 | resizable: true, 28 | }, 29 | ] 30 | 31 | const grid = ( 32 | (idx < 0 ? {} : fields[idx])} 38 | rowsCount={fields.length} 39 | /> 40 | ) 41 | 42 | return ( 43 |
44 |

Type: {type.name}

45 |
46 | 49 |
50 |

Fields

51 | {grid} 52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /client/src/components/schema/TypesTable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react' 7 | 8 | import AutosizeGrid from '../AutosizeGrid' 9 | 10 | export default function TypesTable({ 11 | types, 12 | selectedType, 13 | onChangeSelectedType, 14 | }) { 15 | const columns = [ 16 | { 17 | key: 'name', 18 | name: 'Type', 19 | resizable: true, 20 | sortable: true, 21 | }, 22 | { 23 | key: 'fieldCount', 24 | name: 'Field Count', 25 | resizable: true, 26 | sortable: true, 27 | width: 150, 28 | }, 29 | ] 30 | 31 | const [sortColumn, setSortColumn] = useState('name') 32 | const [sortDirection, setSortDirection] = useState('NONE') 33 | 34 | const handleSort = (sortColumn, sortDirection) => { 35 | setSortColumn(sortColumn) 36 | setSortDirection(sortDirection) 37 | } 38 | 39 | const createRow = (type, index) => { 40 | return { 41 | fieldCount: type.fields.length, 42 | name: type.name, 43 | index, 44 | type, 45 | } 46 | } 47 | 48 | const rows = types.map(createRow) 49 | 50 | if (sortDirection !== 'NONE') { 51 | rows.sort((a, b) => { 52 | const sortDir = sortDirection === 'ASC' ? 1 : -1 53 | 54 | const aValue = React.isValidElement(a[sortColumn]) 55 | ? a[sortColumn].props.datasortkey 56 | : a[sortColumn] 57 | const bValue = React.isValidElement(b[sortColumn]) 58 | ? b[sortColumn].props.datasortkey 59 | : b[sortColumn] 60 | 61 | return aValue > bValue ? sortDir : -sortDir 62 | }) 63 | } 64 | 65 | const onRowClicked = (index) => { 66 | if (index < 0) { 67 | onChangeSelectedType(null) 68 | return 69 | } 70 | onChangeSelectedType(rows[index].type) 71 | } 72 | 73 | return ( 74 | idx >= 0 && rows[idx]} 79 | rowsCount={rows.length} 80 | onGridSort={handleSort} 81 | onRowClick={onRowClicked} 82 | rowSelection={{ 83 | showCheckbox: false, 84 | selectBy: { 85 | keys: { 86 | rowKey: 'name', 87 | values: [selectedType && selectedType.name], 88 | }, 89 | }, 90 | }} 91 | /> 92 | ) 93 | } 94 | -------------------------------------------------------------------------------- /client/src/e2etests/.env: -------------------------------------------------------------------------------- 1 | DGRAPH_VERSION=latest 2 | -------------------------------------------------------------------------------- /client/src/e2etests/README.md: -------------------------------------------------------------------------------- 1 | ## End-to-end tests 2 | 3 | Run tests with Docker Compose config using dev build of Ratel and Puppeteer container: 4 | 5 | ```sh 6 | docker-compose -f docker-compose.dev.yml up 7 | sleep 30 8 | docker exec e2etests_ratel-dev_1 bash -c "cd /workdir && TEST_DGRAPH_SERVER=http://server:8080 JEST_PPTR_DOCKER=1 npm test" 9 | ``` 10 | 11 | Run tests with Docker Compose config using prod build of Ratel and locally installed Puppeteer: 12 | 13 | ```sh 14 | ../../../scripts/test.sh 15 | ``` 16 | -------------------------------------------------------------------------------- /client/src/e2etests/acl-secret.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/ratel/1bab46f712d96e857f0f0be89b1680ffdc00831c/client/src/e2etests/acl-secret.txt -------------------------------------------------------------------------------- /client/src/e2etests/acl/accessDenied.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createTestTab, 10 | setupBrowser, 11 | waitForElement, 12 | waitUntil, 13 | } from '../puppetHelpers' 14 | import { ensureLoggedIn, loginUser, logoutUser } from './aclHelpers' 15 | 16 | let browser = null 17 | let page = null 18 | 19 | beforeAll(async () => { 20 | browser = await setupBrowser() 21 | page = await createTestTab(browser) 22 | 23 | await ensureLoggedIn(page) 24 | }) 25 | 26 | afterAll(async () => browser && (await browser.close())) 27 | 28 | // This test is currently failing, and should be investigated. Commented it to unblock release 29 | test.skip("ACL should show an error if user isn't logged in", async () => { 30 | await logoutUser(page) 31 | 32 | // Close the connection modal and open ACL page. 33 | await page.click('.sidebar-menu a[href="#acl"]') 34 | await page.click('.sidebar-menu a[href="#acl"]') 35 | 36 | // Error message should appear on screen. 37 | await expect( 38 | waitUntil(async () => { 39 | const text = await page.$eval( 40 | `.main-content.acl .acl-view`, 41 | (el) => el.textContent, 42 | ) 43 | return text.includes('You need to login as') 44 | }), 45 | ).resolves.toBeTruthy() 46 | }) 47 | -------------------------------------------------------------------------------- /client/src/e2etests/acl/changePermissions.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createTestTab, 10 | getElementText, 11 | setupBrowser, 12 | waitForElement, 13 | } from '../puppetHelpers' 14 | 15 | import { loginUser, logoutUser } from './aclHelpers' 16 | 17 | let browser = null 18 | 19 | beforeAll(async () => { 20 | jest.setTimeout(10000) 21 | browser = await setupBrowser() 22 | }) 23 | 24 | afterAll(async () => browser && (await browser.close())) 25 | 26 | test("Should persist group's permissions", async () => { 27 | const page = await createTestTab(browser) 28 | 29 | await logoutUser(page) 30 | await expect(loginUser(page, 'groot', 'password')).resolves.toBe(true) 31 | 32 | // First click will close the modal. 33 | await page.click('.sidebar-menu a[href="#acl"]') 34 | await page.click('.sidebar-menu a[href="#acl"]') 35 | 36 | // Groot should always exist. 37 | await waitForElement(page, '.main-content.acl .datagrid div[title=groot]') 38 | 39 | const btnGroups = 40 | '.acl-view .panel.first .btn-toolbar button.btn-sm:nth-child(3)' 41 | await expect(getElementText(page, btnGroups)).resolves.toContain('Groups') 42 | await page.click(btnGroups) 43 | }) 44 | -------------------------------------------------------------------------------- /client/src/e2etests/acl/loginLogout.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { createTestTab, setupBrowser, waitForElement } from '../puppetHelpers' 9 | import { loginUser, logoutUser } from './aclHelpers' 10 | 11 | let browser = null 12 | 13 | beforeAll(async () => { 14 | jest.setTimeout(10000) 15 | browser = await setupBrowser() 16 | }) 17 | 18 | afterAll(async () => browser && (await browser.close())) 19 | 20 | test('ACL login/logout should work', async () => { 21 | const page = await createTestTab(browser) 22 | 23 | await expect(loginUser(page, 'groot', 'password')).resolves.toBe(true) 24 | 25 | await logoutUser(page) 26 | }) 27 | -------------------------------------------------------------------------------- /client/src/e2etests/acl/passwordCheck.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { createTestTab, setupBrowser, waitForElement } from '../puppetHelpers' 9 | 10 | import { ensureLoggedIn, loginUser, logoutUser } from './aclHelpers' 11 | 12 | let browser = null 13 | let page = null 14 | 15 | beforeAll(async () => { 16 | jest.setTimeout(10000) 17 | browser = await setupBrowser() 18 | }) 19 | 20 | afterAll(async () => browser && (await browser.close())) 21 | 22 | test('ACL login should work if the password is correct', async () => { 23 | page = await createTestTab(browser) 24 | await expect(loginUser(page, 'bob', 'R 4 N D O M')).resolves.toBe(false) 25 | await expect(loginUser(page, 'groot', 'password')).resolves.toBe(true) 26 | }) 27 | -------------------------------------------------------------------------------- /client/src/e2etests/acl/showGroot.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { createTestTab, setupBrowser, waitForElement } from '../puppetHelpers' 9 | 10 | import { loginUser, logoutUser } from './aclHelpers' 11 | 12 | let browser = null 13 | 14 | beforeAll(async () => { 15 | jest.setTimeout(10000) 16 | browser = await setupBrowser() 17 | }) 18 | 19 | afterAll(async () => browser && (await browser.close())) 20 | 21 | test('ACL should show users when logged in as groot', async () => { 22 | const page = await createTestTab(browser) 23 | 24 | await logoutUser(page) 25 | await expect(loginUser(page, 'groot', 'password')).resolves.toBe(true) 26 | 27 | // First click will close the modal. 28 | await page.click('.sidebar-menu a[href="#acl"]') 29 | await page.click('.sidebar-menu a[href="#acl"]') 30 | 31 | // Groot should always exist. 32 | await waitForElement(page, '.main-content.acl .datagrid div[title=groot]') 33 | }) 34 | -------------------------------------------------------------------------------- /client/src/e2etests/basic.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | createTestTab, 8 | easyUid, 9 | setupBrowser, 10 | typeAndRun, 11 | waitForActiveTab, 12 | waitForEditor, 13 | waitForFramePreview, 14 | } from './puppetHelpers' 15 | 16 | import { ensureLoggedIn } from './acl/aclHelpers' 17 | 18 | let browser = null 19 | let page = null 20 | 21 | beforeAll(async () => { 22 | jest.setTimeout(10000) 23 | browser = await setupBrowser() 24 | page = await createTestTab(browser) 25 | 26 | await ensureLoggedIn(page) 27 | }) 28 | 29 | afterAll(async () => browser && (await browser.close())) 30 | 31 | test('Should run a query and show results', async () => { 32 | const queryUid = `nodes${easyUid()}` 33 | 34 | await typeAndRun( 35 | page, 36 | `{ 37 | ${queryUid}(func: type(Node)) { 38 | uid 39 | expand(_all_) 40 | `, 41 | ) 42 | 43 | await expect(waitForFramePreview(page, queryUid)).resolves.toBeTruthy() 44 | await expect(waitForActiveTab(page)).resolves.toBe('Graph') 45 | }) 46 | -------------------------------------------------------------------------------- /client/src/e2etests/bigInteger.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createHttpClient, 10 | createTestTab, 11 | easyUid, 12 | setupBrowser, 13 | typeAndRun, 14 | waitForActiveTab, 15 | waitForEditor, 16 | waitForElement, 17 | waitForFramePreview, 18 | } from './puppetHelpers' 19 | 20 | import { ensureLoggedIn } from './acl/aclHelpers' 21 | 22 | let browser = null 23 | let page = null 24 | 25 | beforeAll(async () => { 26 | jest.setTimeout(10000) 27 | jest.retryTimes(5) 28 | 29 | browser = await setupBrowser() 30 | page = await createTestTab(browser) 31 | 32 | await ensureLoggedIn(page) 33 | }) 34 | 35 | afterAll(async () => browser && (await browser.close())) 36 | 37 | test('Should draw one to one nodes', async () => { 38 | const testId = `testRun${easyUid()}` 39 | 40 | const httpClient = await createHttpClient() 41 | await httpClient.alter({ schema: `${testId}_money: int .` }) 42 | await httpClient.newTxn().mutate({ 43 | setJson: { 44 | [testId + '_money']: '1193880128115965952', 45 | }, 46 | commitNow: true, 47 | }) 48 | 49 | await typeAndRun( 50 | page, 51 | `{ 52 | query(func: has(${testId}_money)) { 53 | uid 54 | ${testId}_money 55 | `, 56 | ) 57 | 58 | await expect( 59 | waitForFramePreview(page, `${testId}_money`), 60 | ).resolves.toBeTruthy() 61 | 62 | await page.click('.panel.second a#frame-tabs-tab-json') 63 | await waitForElement(page, '.frame-code-tab pre') 64 | 65 | await expect( 66 | page.$eval('.frame-code-tab pre', (el) => el.textContent), 67 | ).resolves.toContain('1193880128115965952') 68 | }) 69 | -------------------------------------------------------------------------------- /client/src/e2etests/dataExplorer.test.js-DISABLED: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from "puppeteer"; 7 | 8 | import { 9 | createHttpClient, 10 | createTestTab, 11 | easyUid, 12 | setupBrowser, 13 | typeAndRun, 14 | waitForEditor, 15 | waitForElement, 16 | } from "./puppetHelpers"; 17 | 18 | let browser = null; 19 | 20 | beforeAll(async () => { 21 | browser = await setupBrowser(); 22 | }); 23 | 24 | afterAll(async () => browser && (await browser.close())); 25 | 26 | test("DataExplorer should display something", async () => { 27 | // Insert test nodes. 28 | const testId = `dataExplorer_test_${easyUid()}`; 29 | const mutationRes = createHttpClient() 30 | .newTxn() 31 | .mutate({ 32 | commitNow: true, 33 | mutation: ` 34 | { set { 35 | <_:node> "${Date.now()}" . 36 | <_:node> "${Date.now()}" . 37 | } }`, 38 | }); 39 | 40 | const page = await createTestTab(browser); 41 | await waitForEditor(page); 42 | 43 | // Make sure mutation was successful 44 | await expect(mutationRes).resolves.toHaveProperty("data.code", "Success"); 45 | 46 | // Click the "Data Explorer" button. 47 | await page.click('.sidebar-menu a[href="#dataexplorer"]'); 48 | 49 | // After clicking "Data Explorer" the test predicates should show up in DE. 50 | await expect( 51 | waitForElement( 52 | page, 53 | `.main-content.dataexplorer div[title=ALPHA_${testId}]`, 54 | ), 55 | ).resolves.toBeTruthy(); 56 | 57 | await expect( 58 | waitForElement( 59 | page, 60 | `.main-content.dataexplorer div[title=BRAVO_${testId}]`, 61 | ), 62 | ).resolves.toBeTruthy(); 63 | }); 64 | -------------------------------------------------------------------------------- /client/src/e2etests/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | ratel-dev: 4 | build: 5 | context: ./ 6 | dockerfile: ./pptr.Dockerfile 7 | init: true 8 | command: ["/bin/bash", "-c", "cd /workdir && npm install && npm start"] 9 | ports: 10 | - 3000:3000 11 | volumes: 12 | - ../../package.json:/workdir/package.json 13 | - ../../config:/workdir/config 14 | - ../../public:/workdir/public 15 | - ../../scripts:/workdir/scripts 16 | - ../:/workdir/src 17 | zero: 18 | image: dgraph/dgraph:master 19 | volumes: 20 | - type: volume 21 | source: dgraph 22 | target: /dgraph 23 | volume: 24 | nocopy: true 25 | ports: 26 | - 5080:5080 27 | - 6080:6080 28 | restart: on-failure 29 | command: dgraph zero --my=zero:5080 30 | server: 31 | image: dgraph/dgraph:master 32 | volumes: 33 | - type: volume 34 | source: dgraph 35 | target: /dgraph 36 | volume: 37 | nocopy: true 38 | - ./acl-secret.txt:/secrets/acl-secret.txt 39 | ports: 40 | - 8080:8080 41 | - 9080:9080 42 | restart: on-failure 43 | command: 44 | dgraph alpha --my=server:8080 --zero=zero:5080 --acl_secret_file=/secrets/acl-secret.txt 45 | volumes: 46 | dgraph: 47 | -------------------------------------------------------------------------------- /client/src/e2etests/docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | # Ratel binary and test environment are built from Dockerfile and Dockerfile.test 2 | # Dgraph server versions are set to 'latest' and can be overriden by setting 3 | # DGRAPH_VERSION env var 4 | version: "3.7" 5 | services: 6 | ratel: 7 | build: ../../.. 8 | ports: 9 | - 8000 10 | 11 | test: 12 | build: 13 | context: ../../.. 14 | dockerfile: test.Dockerfile 15 | privileged: true 16 | command: ["/bin/sh", "-c", "trap : TERM INT; sleep 9999999999d & wait"] 17 | environment: 18 | TEST_DGRAPH_SERVER: http://e2etests_alpha_1:8080 19 | TEST_RATEL_URL: http://e2etests_ratel_1:8000?local 20 | 21 | zero: 22 | image: dgraph/dgraph:${DGRAPH_VERSION} 23 | ports: 24 | - 5080 25 | - 6080 26 | restart: on-failure 27 | command: dgraph zero --my=e2etests_zero_1:5080 28 | 29 | alpha: 30 | image: dgraph/dgraph:${DGRAPH_VERSION} 31 | volumes: 32 | - ./acl-secret.txt:/secrets/acl-secret.txt 33 | ports: 34 | - 8080 35 | - 9080 36 | restart: on-failure 37 | command: 38 | dgraph alpha --my=e2etests_alpha_1:7080 --zero=e2etests_zero_1:5080 39 | --acl='secret-file=/secrets/acl-secret.txt' 40 | --security='whitelist=10.0.0.0/8,172.0.0.0/8,192.168.0.0/16' 41 | -------------------------------------------------------------------------------- /client/src/e2etests/expandGraph.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createHttpClient, 10 | createTestTab, 11 | easyUid, 12 | setupBrowser, 13 | typeAndRun, 14 | waitForEditor, 15 | waitForElement, 16 | waitForElementDisappear, 17 | } from './puppetHelpers' 18 | 19 | import { ensureLoggedIn } from './acl/aclHelpers' 20 | 21 | let browser = null 22 | let page = null 23 | 24 | beforeAll(async () => { 25 | jest.setTimeout(10000) 26 | jest.retryTimes(5) 27 | 28 | browser = await setupBrowser() 29 | page = await createTestTab(browser) 30 | 31 | await ensureLoggedIn(page) 32 | }) 33 | 34 | afterAll(async () => browser && (await browser.close())) 35 | 36 | // Test for https://github.com/hypermodeinc/ratel/issues/93 37 | test('Clicking must update the graph', async () => { 38 | // Insert test nodes. 39 | const N = 678 40 | const testId = `testRun${easyUid()}` 41 | const nodes = [] 42 | for (let i = 0; i < N; i++) { 43 | nodes.push(`<_:node${i}> <${testId}> "node ${i}" .`) 44 | } 45 | const httpClient = await createHttpClient() 46 | const mutationRes = httpClient.newTxn().mutate({ 47 | commitNow: true, 48 | mutation: ` 49 | { set { 50 | ${nodes.join('\n')} 51 | } }`, 52 | }) 53 | 54 | // Make sure mutation was successful 55 | await expect(mutationRes).resolves.toHaveProperty('data.code', 'Success') 56 | 57 | await typeAndRun( 58 | page, 59 | `{ 60 | query(func: has(${testId})) { 61 | uid 62 | ${testId} 63 | `, 64 | ) 65 | 66 | const expandBtnSelector = '.partial-render-info button.btn-link' 67 | await expect(waitForElement(page, expandBtnSelector)).resolves.toBeTruthy() 68 | 69 | await expect( 70 | page.$eval(expandBtnSelector, (el) => el.textContent), 71 | ).resolves.toBe(`Expand remaining ${N - 400} nodes.`) 72 | 73 | // Click the "Expand remaining" button. 74 | await page.click(expandBtnSelector) 75 | 76 | // After clicking "Expand remaining" it should expand graph and disappear. 77 | await expect(waitForElementDisappear(page, expandBtnSelector)).resolves.toBe( 78 | true, 79 | ) 80 | }) 81 | -------------------------------------------------------------------------------- /client/src/e2etests/jsonMutation.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createTestTab, 10 | setupBrowser, 11 | typeAndRun, 12 | waitForActiveTab, 13 | waitForEditor, 14 | } from './puppetHelpers' 15 | 16 | import { ensureLoggedIn } from './acl/aclHelpers' 17 | 18 | let browser = null 19 | let page = null 20 | 21 | beforeAll(async () => { 22 | jest.setTimeout(10000) 23 | browser = await setupBrowser() 24 | page = await createTestTab(browser) 25 | 26 | await ensureLoggedIn(page) 27 | }) 28 | 29 | afterAll(async () => browser && (await browser.close())) 30 | 31 | test('Should execute JSON mutations', async () => { 32 | await page.click('.editor-panel input.editor-type[value=mutate]') 33 | await page.click('.editor-panel .CodeMirror') 34 | 35 | await typeAndRun(page, `{ "set": [ { "name": "Alice" } ] }`) 36 | 37 | await expect(waitForActiveTab(page)).resolves.toBe('Message') 38 | }) 39 | -------------------------------------------------------------------------------- /client/src/e2etests/noDuplicateMutations.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createTestTab, 10 | setupBrowser, 11 | typeAndRun, 12 | waitForActiveTab, 13 | waitForEditor, 14 | waitForElement, 15 | } from './puppetHelpers' 16 | 17 | import { ensureLoggedIn } from './acl/aclHelpers' 18 | 19 | let browser = null 20 | let page = null 21 | 22 | beforeAll(async () => { 23 | jest.setTimeout(15000) 24 | browser = await setupBrowser() 25 | page = await createTestTab(browser) 26 | 27 | await ensureLoggedIn(page) 28 | }) 29 | 30 | afterAll(async () => browser && (await browser.close())) 31 | 32 | // skipping the below test since it's needs deeper investigation 33 | test.skip('Should execute mutations only once', async () => { 34 | const mutations = [] 35 | 36 | await page.setRequestInterception(true) 37 | page.on('request', (netRequest) => { 38 | if (netRequest.url().indexOf('/mutate') >= 0) { 39 | mutations.push(netRequest.url()) 40 | } 41 | netRequest.continue() 42 | }) 43 | 44 | await waitForEditor(page) 45 | 46 | await page.click('.editor-panel input.editor-type[value=mutate]') 47 | await page.click('.editor-panel .CodeMirror') 48 | 49 | expect(mutations).toHaveLength(0) 50 | 51 | // Submit a mutation 52 | await typeAndRun(page, `{ "set": [ { "name": "Alice" } ] }`) 53 | await expect(waitForActiveTab(page)).resolves.toBe('Message') 54 | 55 | expect(mutations).toHaveLength(1) 56 | 57 | // Do some clicking around 58 | await page.click(".sidebar-menu a[href='#schema']") 59 | await waitForElement(page, '.btn-toolbar.schema-toolbar') 60 | 61 | await page.click(".sidebar-menu a[href='#info']") 62 | await waitForElement(page, '.sidebar-content.open .sidebar-help') 63 | 64 | // Go back to console 65 | await page.click(".sidebar-menu a[href='#']") 66 | await expect(waitForActiveTab(page)).resolves.toBe('Message') 67 | 68 | expect(mutations).toHaveLength(1, "Ratel shouldn't send duplicate mutations") 69 | }) 70 | -------------------------------------------------------------------------------- /client/src/e2etests/oneToOne.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createHttpClient, 10 | createTestTab, 11 | easyUid, 12 | setupBrowser, 13 | typeAndRun, 14 | waitForActiveTab, 15 | waitForEditor, 16 | waitForElement, 17 | } from './puppetHelpers' 18 | 19 | import { ensureLoggedIn } from './acl/aclHelpers' 20 | 21 | let browser = null 22 | let page = null 23 | 24 | beforeAll(async () => { 25 | jest.setTimeout(10000) 26 | jest.retryTimes(5) 27 | 28 | browser = await setupBrowser() 29 | page = await createTestTab(browser) 30 | 31 | await ensureLoggedIn(page) 32 | }) 33 | 34 | afterAll(async () => browser && (await browser.close())) 35 | 36 | test('Should draw one to one nodes', async () => { 37 | const testId = `testRun${easyUid()}` 38 | 39 | const httpClient = await createHttpClient() 40 | await httpClient.alter({ schema: `${testId}: uid .` }) 41 | await httpClient.newTxn().mutate({ 42 | setJson: { 43 | [testId + '_name']: 'Alice', 44 | [testId]: { 45 | [testId + '_name']: 'Bob', 46 | }, 47 | }, 48 | commitNow: true, 49 | }) 50 | 51 | await typeAndRun( 52 | page, 53 | `{ 54 | query(func: has(${testId})) { 55 | uid 56 | ${testId} { uid } 57 | `, 58 | ) 59 | 60 | const summarySelector = '.graph-overlay .title' 61 | await waitForElement(page, summarySelector) 62 | 63 | await expect( 64 | page.$eval(summarySelector, (el) => el.textContent), 65 | ).resolves.toBe('Showing 2 nodes and 1 edges') 66 | }) 67 | -------------------------------------------------------------------------------- /client/src/e2etests/pptr.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-slim 2 | 3 | # Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) 4 | # Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer 5 | # installs, work. 6 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ 7 | && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ 8 | && apt-get update \ 9 | && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \ 10 | --no-install-recommends \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | RUN apt-get update && apt-get install -y bash git 14 | 15 | RUN mkdir /workdir 16 | RUN chmod 0777 /workdir 17 | 18 | # Install puppeteer so it's available in the container. 19 | RUN npm i puppeteer@1.11.0 \ 20 | # Add user so we don't need --no-sandbox. 21 | # same layer as npm install to keep re-chowned files from using up several hundred MBs more space 22 | && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \ 23 | && mkdir -p /home/pptruser/Downloads \ 24 | && chown -R pptruser:pptruser /home/pptruser \ 25 | && chown -R pptruser:pptruser /node_modules 26 | 27 | # Run everything after as non-privileged user. 28 | USER pptruser 29 | 30 | CMD ["true"] 31 | -------------------------------------------------------------------------------- /client/src/e2etests/queryTimeout.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { 9 | createTestTab, 10 | setupBrowser, 11 | typeAndRun, 12 | waitForActiveTab, 13 | waitForEditor, 14 | waitForElement, 15 | waitForElementDisappear, 16 | } from './puppetHelpers' 17 | 18 | import { ensureLoggedIn } from './acl/aclHelpers' 19 | 20 | let browser = null 21 | let page = null 22 | 23 | beforeAll(async () => { 24 | jest.setTimeout(10000) 25 | browser = await setupBrowser() 26 | page = await createTestTab(browser) 27 | 28 | await ensureLoggedIn(page) 29 | }) 30 | 31 | afterAll(async () => browser && (await browser.close())) 32 | 33 | test('Should send query timeout to server', async () => { 34 | const queries = [] 35 | 36 | await page.setRequestInterception(true) 37 | page.on('request', (netRequest) => { 38 | if (netRequest.url().indexOf('/query') >= 0) { 39 | queries.push(netRequest.url()) 40 | } 41 | netRequest.continue() 42 | }) 43 | 44 | // Use different timeout on every test run 45 | const timeoutValue = Math.ceil(Math.random() * 1000) 46 | 47 | const extraSettingsTab = '#connection-settings-tabs-tab-extra-settings' 48 | const timeoutInput = '.modal.server-connection #queryTimeoutInput' 49 | 50 | await page.click(".sidebar-menu a[href='#connection']") 51 | 52 | await waitForElement(page, extraSettingsTab) 53 | await page.click(extraSettingsTab) 54 | await waitForElement(page, timeoutInput) 55 | 56 | await page.click(timeoutInput) 57 | await page.evaluate(() => document.execCommand('selectall', false, null)) 58 | await page.type(timeoutInput, `${timeoutValue}`) 59 | 60 | await page.click('.modal-dialog button.close') 61 | await waitForElementDisappear(page, '.sidebar-content.open') 62 | 63 | // "Forget" any queries not related to this test 64 | queries.splice(0, queries.length) 65 | 66 | // Send a query 67 | await waitForEditor(page) 68 | await page.click('.editor-panel .CodeMirror') 69 | 70 | await typeAndRun(page, ' { q(func: uid(1)) { uid } } ') 71 | await waitForActiveTab(page) 72 | 73 | await expect(waitForActiveTab(page)).resolves.toBe('Graph') 74 | 75 | expect(queries[0]).toContain(`timeout=${timeoutValue}s`) 76 | }) 77 | -------------------------------------------------------------------------------- /client/src/e2etests/typeSystem.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import puppeteer from 'puppeteer' 7 | 8 | import { loginUser } from './acl/aclHelpers' 9 | import { 10 | createTestTab, 11 | findElementWithText, 12 | setupBrowser, 13 | waitForElement, 14 | waitForElementDisappear, 15 | } from './puppetHelpers' 16 | 17 | import { ensureLoggedIn } from './acl/aclHelpers' 18 | 19 | let browser = null 20 | let page = null 21 | 22 | beforeAll(async () => { 23 | jest.setTimeout(10000) 24 | browser = await setupBrowser() 25 | page = await createTestTab(browser) 26 | 27 | await ensureLoggedIn(page) 28 | }) 29 | 30 | afterAll(async () => browser && (await browser.close())) 31 | 32 | test('Should accept i18n characters in type names', async () => { 33 | // Click the "Schema" button. 34 | await page.click('.sidebar-menu a[href="#schema"]') 35 | 36 | // Wait for schema to render. 37 | const schemaBtnSelector = '.schema .panel.first .schema-toolbar button.btn' 38 | 39 | const typesBtn = await findElementWithText(page, schemaBtnSelector, 'Types') 40 | 41 | await typesBtn.click() 42 | 43 | await page.click('.schema-toolbar button.btn.btn-primary') 44 | 45 | const typeNameInput = '.modal.show input#typeName.form-control' 46 | await waitForElement(page, typeNameInput) 47 | await page.click(typeNameInput) 48 | await page.keyboard.type('WeirdТайп') 49 | 50 | await page.click('.modal.show .modal-footer button.btn.btn-primary') 51 | 52 | // If the modal has disappeared then a type was created without errors. 53 | await waitForElementDisappear(page, '.modal.show') 54 | await waitForElementDisappear(page, '.fade.modal-backdrop.show') 55 | }) 56 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import 'core-js/stable' 7 | import 'regenerator-runtime/runtime' 8 | 9 | import React from 'react' 10 | import ReactDOM from 'react-dom' 11 | 12 | import App from './containers/App' 13 | import AppProvider from './containers/AppProvider' 14 | 15 | window.FontAwesomeConfig = { autoReplaceSvg: 'nest' } 16 | require('@fortawesome/fontawesome-free/js/all.min.js') 17 | 18 | export function render(Component) { 19 | return ReactDOM.render( 20 | , 21 | document.getElementById('root') || document.createElement('div'), 22 | ) 23 | } 24 | 25 | render(App) 26 | 27 | if (module.hot) { 28 | window.RATEL_DEV_MODE = true 29 | module.hot.accept('./containers/App', () => { 30 | const nextApp = require('./containers/App').default 31 | render(nextApp) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /client/src/index.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import jsdom from 'jsdom' 7 | import React from 'react' 8 | 9 | import App from './containers/App' 10 | import AppProvider from './containers/AppProvider' 11 | 12 | import { render } from './index' 13 | 14 | const document = new jsdom.JSDOM('') 15 | global.document = document 16 | global.window = document.defaultView 17 | global.document.body.createTextRange = () => ({ 18 | getBoundingClientRect: () => ({ right: 0 }), 19 | getClientRects: () => ({ 20 | left: 0, 21 | length: 0, 22 | right: 0, 23 | }), 24 | setEnd: () => {}, 25 | setStart: () => {}, 26 | }) 27 | 28 | test('Creating AppProvider should not throw errors', () => { 29 | expect().toBeTruthy() 30 | }) 31 | 32 | test('Rendering App should not throw errors', (done) => { 33 | render(App) 34 | setTimeout(done, 2) 35 | }) 36 | -------------------------------------------------------------------------------- /client/src/lib/ColorGenerator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import randomColor from 'randomcolor' 7 | 8 | export default class ColorGenerator { 9 | // Picked up from http://graphicdesign.stackexchange.com/questions/3682/where-can-i-find-a-large-palette-set-of-contrasting-colors-for-coloring-many-d. 10 | randomColorList = [ 11 | '#47c0ee', 12 | '#8dd593', 13 | '#f6c4e1', 14 | '#8595e1', 15 | '#f0b98d', 16 | '#f79cd4', 17 | '#bec1d4', 18 | '#11c638', 19 | '#b5bbe3', 20 | '#7d87b9', 21 | '#e07b91', 22 | '#4a6fe3', 23 | ] 24 | 25 | get = () => this.randomColorList.shift() || randomColor() 26 | 27 | getRGBA = (alpha = 1) => { 28 | const col = this.get() 29 | const component = (idx) => 30 | parseInt(col.substring(1 + idx * 2, 3 + idx * 2), 16) 31 | return [component(0), component(1), component(2), alpha] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/lib/SchemaGraphParser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import produce from 'immer' 7 | 8 | import { GraphParser } from './graph' 9 | 10 | export default class SchemaGraphParser extends GraphParser { 11 | addResponseToQueue(data) { 12 | data = produce(data, (data) => { 13 | if (data.schema) { 14 | data.schema.forEach((p) => { 15 | p.uid = p.name = p.predicate 16 | }) 17 | } 18 | 19 | if (data.types) { 20 | data.types.forEach((type) => { 21 | type.fields.forEach((f) => { 22 | f.uid = f.name 23 | }) 24 | }) 25 | } 26 | }) 27 | 28 | return super.addResponseToQueue(data) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client/src/lib/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export const Unknown = Symbol('Unknown') 7 | export const Fetching = Symbol('Fetching') 8 | export const FetchError = Symbol('FetchError') 9 | export const OK = Symbol('OK') 10 | 11 | export const LoggedIn = Symbol('LoggedIn') 12 | export const Anonymous = Symbol('Anonymous') 13 | 14 | export const QUERY_TIMEOUT_DEFAULT = 20 // Seconds 15 | export const SERVER_HISTORY_LENGTH = 5 16 | -------------------------------------------------------------------------------- /client/src/lib/dgraph-syntax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { Unknown } from './constants' 7 | 8 | const LATEST_VERSION = 'v20.11.0' 9 | 10 | export function isLatestVersion(ver) { 11 | return ver === Unknown || ver.indexOf(LATEST_VERSION) === 0 12 | } 13 | 14 | export function getPredicateTypeString(predicate) { 15 | let type = predicate.type 16 | const lang = type === 'string' && predicate.lang ? '@lang' : '' 17 | if (predicate.list) { 18 | type = '[' + type + ']' 19 | } 20 | 21 | const hasIndex = !!predicate.index 22 | let tokenizers = '' 23 | let upsert = '' 24 | if (hasIndex) { 25 | tokenizers = predicate.tokenizer.join(', ') 26 | upsert = predicate.upsert ? '@upsert' : '' 27 | } 28 | 29 | return [ 30 | type, 31 | hasIndex ? `@index(${tokenizers})` : '', 32 | lang, 33 | upsert, 34 | predicate.count ? '@count' : '', 35 | predicate.reverse ? '@reverse' : '', 36 | ] 37 | .filter((x) => x.length) 38 | .join(' ') 39 | } 40 | 41 | export function getPredicateQuery(predicate) { 42 | return `<${predicate.predicate}>: ${getPredicateTypeString(predicate)} .` 43 | } 44 | 45 | export const isUserType = (typeName) => 46 | (typeName || '').indexOf('dgraph.type.') !== 0 && 47 | typeName !== 'dgraph.graphql' 48 | 49 | export const isUserPredicate = (name) => 50 | [ 51 | '_predicate_', 52 | '_share_', 53 | '_share_hash_', 54 | 'dgraph.group', 55 | 'dgraph.group.acl', 56 | 'dgraph.password', 57 | 'dgraph.user.group', 58 | 'dgraph.type', 59 | 'dgraph.xid', 60 | ].indexOf(name) < 0 61 | 62 | export const isAclPredicate = (name) => 63 | isUserPredicate(name) || name === 'dgraph.type' 64 | 65 | export function getRawSchema(schema, types = []) { 66 | const schemaStrings = 67 | (schema && 68 | schema 69 | .filter((p) => isUserPredicate(p.predicate)) 70 | .map((p) => getPredicateQuery(p))) || 71 | [] 72 | 73 | const typeDefs = types.map((t) => 74 | ` 75 | type <${t.name}> { 76 | ${t.fields.map((f) => `\t${f.name}`).join('\n')} 77 | }`.trim(), 78 | ) 79 | return [...schemaStrings.sort(), ...typeDefs].join('\n') 80 | } 81 | -------------------------------------------------------------------------------- /client/src/lib/graph.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { processGraph } from './graph' 7 | 8 | // TODO: reenable these tests, broken by switch from nodes [] to vis.DataSet 9 | 10 | test('Temporary test', () => { 11 | expect(true).toBe(true) 12 | }) 13 | 14 | /* 15 | 16 | test("Graph with 2 central nodes should look OK", () => { 17 | const data = require("./test_data/small_graph_1.json"); 18 | expect(processGraph(data, false, "")).toMatchSnapshot(); 19 | expect(processGraph(data, true, "")).toMatchSnapshot(); 20 | }); 21 | 22 | test("10 movies with countries", () => { 23 | const data = require("./test_data/10_movies_with_countries.json"); 24 | expect(processGraph(data, false, "")).toMatchSnapshot(); 25 | expect(processGraph(data, true, "")).toMatchSnapshot(); 26 | }); 27 | 28 | test("Regexes should work", () => { 29 | const data = require("./test_data/10_movies_with_countries.json"); 30 | expect(processGraph(data, false, "Foo")).toMatchSnapshot(); 31 | expect(processGraph(data, false, "Bar")).toMatchSnapshot(); 32 | expect(processGraph(data, false, "u")).toMatchSnapshot(); 33 | expect(processGraph(data, false, "ui")).toMatchSnapshot(); 34 | expect(processGraph(data, false, "uid")).toMatchSnapshot(); 35 | 36 | expect(processGraph(data, false, "nam")).toMatchSnapshot(); 37 | expect(processGraph(data, false, "name")).toMatchSnapshot(); 38 | }); 39 | 40 | test("Node colors should not change when predicates are re-ordered in JSON", () => { 41 | function processedNodesWithoutId(query) { 42 | const nodes = processGraph(query, false, "").nodes; 43 | nodes.forEach(n => delete n.id); 44 | return nodes; 45 | } 46 | const graph1 = require("./test_data/star_wars_colors_1.json"); 47 | const graph2 = require("./test_data/star_wars_colors_2.json"); 48 | expect(processedNodesWithoutId(graph1)).toEqual( 49 | processedNodesWithoutId(graph2), 50 | ); 51 | }); 52 | 53 | test("processGraph should not ignore `extensions`", () => { 54 | const data = { 55 | extensions: [{ uid: 100 }, { uid: 200 }, { uid: 300 }], 56 | }; 57 | expect(processGraph(data, false, "").nodes.length).toBe(3); 58 | }); 59 | 60 | */ 61 | -------------------------------------------------------------------------------- /client/src/lib/parsers/queryVars.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | // import peg from "pegjs"; 7 | // 8 | // export function makeMutationParser() { 9 | // return peg.generate(` 10 | // query = _ 'query' _ name:identifier _ '(' _ vars:varDefs _ ')' _ queryBody { 11 | // return {vars}; 12 | // } 13 | // 14 | // identifier = [_a-zA-Z.] [_a-zA-Z0-9.]+ 15 | // 16 | // varDefs = varDef / varDef _ ',' _ varDefs / _ 17 | // 18 | // varDef = name:varName _ ':' _ type:identifier { return {name, type} } 19 | // 20 | // varName = '$' identifier { return identifier } 21 | // 22 | // _ = [ \t\n\r\a]* 23 | // queryBody = (./[ \t\n\r\a])* 24 | // `); 25 | // } 26 | 27 | function replaceAll(s, from, to) { 28 | while (true) { 29 | const s2 = s.replace(from, to) 30 | if (s === s2) { 31 | return s 32 | } 33 | s = s2 34 | } 35 | } 36 | 37 | export function extractVars(query) { 38 | let q = replaceAll(query, '\n', '') 39 | q = replaceAll(q, '\r', '') 40 | q = replaceAll(q, '\t', '') 41 | q = replaceAll(q, ' ', '') 42 | 43 | const varsBlockMatch = q.match(/query[a-zA-Z0-9._]*\(([^)]+)\)/) 44 | if (varsBlockMatch?.index !== 0) { 45 | return [] 46 | } 47 | const varList = varsBlockMatch[1] 48 | return [...varList.matchAll(/\$([^:]+):([^,]+)/g)].map((m) => [m[1], m[2]]) 49 | } 50 | -------------------------------------------------------------------------------- /client/src/lib/test_data/10_movies_with_countries.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": [ 3 | { 4 | "uid": "0x788f", 5 | "name@en:.": "Ulbo Garvema", 6 | "count(genre)": 1, 7 | "country": [ 8 | { 9 | "name@en:.": "Netherlands", 10 | "uid": "0x7975c1" 11 | } 12 | ] 13 | }, 14 | { 15 | "uid": "0x7895", 16 | "name@en:.": "Surcos de sangre", 17 | "count(genre)": 1, 18 | "country": [ 19 | { 20 | "name@en:.": "Argentina", 21 | "uid": "0x8de824" 22 | } 23 | ] 24 | }, 25 | { 26 | "uid": "0x789b", 27 | "name@en:.": "Grey Matter", 28 | "count(genre)": 1, 29 | "country": [ 30 | { 31 | "name@en:.": "Australia", 32 | "uid": "0x432385" 33 | }, 34 | { 35 | "name@en:.": "Rwanda", 36 | "uid": "0x6fb344" 37 | } 38 | ] 39 | }, 40 | { 41 | "uid": "0x78a9", 42 | "name@en:.": "Rudyard Kipling: A Remembrance Tale", 43 | "count(genre)": 1, 44 | "country": [ 45 | { 46 | "name@en:.": "United Kingdom", 47 | "uid": "0x3519c5" 48 | } 49 | ] 50 | }, 51 | { 52 | "uid": "0x78ac", 53 | "name@en:.": "Dreamers", 54 | "count(genre)": 2, 55 | "country": [ 56 | { 57 | "uid": "0x480594" 58 | } 59 | ] 60 | }, 61 | { 62 | "uid": "0x78b2", 63 | "name@en:.": "Ligabue", 64 | "count(genre)": 2, 65 | "country": [ 66 | { 67 | "name@en:.": "Italy", 68 | "uid": "0xe57e1" 69 | }, 70 | { 71 | "name@en:.": "France", 72 | "uid": "0x3f52f1" 73 | } 74 | ] 75 | }, 76 | { 77 | "uid": "0x78b7", 78 | "name@en:.": "The Blue Mansion", 79 | "count(genre)": 1, 80 | "country": [ 81 | { 82 | "name@en:.": "Singapore", 83 | "uid": "0x4d35d1" 84 | } 85 | ] 86 | }, 87 | { 88 | "uid": "0x78cd", 89 | "name@en:.": "His Excellency Mr. Minister", 90 | "count(genre)": 3 91 | }, 92 | { 93 | "uid": "0x78e4", 94 | "name@en:.": "Iris", 95 | "count(genre)": 1, 96 | "country": [ 97 | { 98 | "name@en:.": "Canada", 99 | "uid": "0x9a6b46" 100 | } 101 | ] 102 | }, 103 | { 104 | "uid": "0x78e9", 105 | "name@en:.": "Le dolci zie", 106 | "count(genre)": 1, 107 | "country": [ 108 | { 109 | "name@en:.": "Italy", 110 | "uid": "0xe57e1" 111 | } 112 | ] 113 | } 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /client/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export const getSpace = (tablet) => 7 | tablet ? tablet.space || tablet.onDiskBytes : null 8 | -------------------------------------------------------------------------------- /client/src/reducers/backup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | DEFAULT_BACKUP_CONFIG, 8 | SAVE_BACKUP_ERROR, 9 | SAVE_BACKUP_RESULT, 10 | SAVE_START_BACKUP, 11 | SET_BACKUP_CONFIG, 12 | } from 'actions/backup' 13 | import produce from 'immer' 14 | 15 | const defaultState = { 16 | config: DEFAULT_BACKUP_CONFIG, 17 | backups: [], 18 | } 19 | 20 | export default (state = defaultState, action) => 21 | produce(state, (draft) => { 22 | switch (action.type) { 23 | case SET_BACKUP_CONFIG: 24 | Object.assign(draft.config, action.payload) 25 | break 26 | 27 | case SAVE_START_BACKUP: 28 | const { backupId, config, serverUrl, startTime } = action 29 | draft.backups.push({ backupId, config, startTime, serverUrl }) 30 | break 31 | 32 | case SAVE_BACKUP_ERROR: { 33 | const { backupId, err } = action 34 | draft.backups.find((b) => b.backupId === backupId).error = err 35 | break 36 | } 37 | 38 | case SAVE_BACKUP_RESULT: { 39 | const { backupId, result } = action 40 | draft.backups.find((b) => b.backupId === backupId).result = result 41 | break 42 | } 43 | 44 | default: 45 | return 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /client/src/reducers/cluster.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import produce from 'immer' 7 | 8 | import { 9 | GET_CLUSTER_STATE_RESULT, 10 | GET_INSTANCE_HEALTH_RESULT, 11 | SET_IS_AUTHORIZED, 12 | } from 'actions/cluster' 13 | 14 | const defaultState = { 15 | instanceHealth: null, 16 | clusterState: null, 17 | } 18 | 19 | export default (state = defaultState, action) => 20 | produce(state, (draft) => { 21 | switch (action.type) { 22 | case GET_INSTANCE_HEALTH_RESULT: 23 | draft.instanceHealth = action.json 24 | break 25 | 26 | case GET_CLUSTER_STATE_RESULT: 27 | draft.clusterState = action.json 28 | break 29 | 30 | case SET_IS_AUTHORIZED: 31 | draft.isAuthorized = action.isAuthorized 32 | break 33 | 34 | default: 35 | return 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { persistCombineReducers } from 'redux-persist' 7 | 8 | import backup from './backup' 9 | import cluster from './cluster' 10 | import connection from './connection' 11 | import frames from './frames' 12 | import query from './query' 13 | import ui from './ui' 14 | 15 | export default function makeRootReducer(config) { 16 | return persistCombineReducers(config, { 17 | backup, 18 | cluster, 19 | connection, 20 | frames, 21 | query, 22 | ui, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /client/src/reducers/query.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | UPDATE_ACTION, 8 | UPDATE_BEST_EFFORT, 9 | UPDATE_QUERY, 10 | UPDATE_QUERY_AND_ACTION, 11 | UPDATE_QUERY_VARS, 12 | UPDATE_READ_ONLY, 13 | } from 'actions/query' 14 | import produce from 'immer' 15 | 16 | const defaultState = { 17 | query: '', 18 | queryVars: [], 19 | allQueries: { query: '', mutate: '', alter: '' }, 20 | action: 'query', 21 | readOnly: false, 22 | bestEffort: false, 23 | } 24 | 25 | export default (state = defaultState, action) => 26 | produce(state, (draft) => { 27 | draft.allQueries = draft.allQueries || {} 28 | draft.action = draft.action || 'query' 29 | draft.allQueries[draft.action] = draft.query || '' 30 | 31 | switch (action.type) { 32 | case UPDATE_QUERY: 33 | draft.query = action.query 34 | break 35 | 36 | case UPDATE_ACTION: 37 | draft.action = action.action 38 | draft.query = draft.allQueries[draft.action] 39 | break 40 | 41 | case UPDATE_QUERY_AND_ACTION: 42 | draft.action = action.action 43 | draft.query = action.query 44 | break 45 | 46 | case UPDATE_READ_ONLY: 47 | draft.readOnly = action.readOnly 48 | draft.bestEffort = action.readOnly 49 | break 50 | 51 | case UPDATE_BEST_EFFORT: 52 | draft.bestEffort = action.bestEffort 53 | break 54 | 55 | case UPDATE_QUERY_VARS: 56 | draft.queryVars = action.newVars 57 | break 58 | 59 | default: 60 | break 61 | } 62 | 63 | draft.allQueries[draft.action] = draft.query 64 | }) 65 | -------------------------------------------------------------------------------- /client/src/reducers/ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import produce from 'immer' 7 | 8 | import { 9 | CLICK_SIDEBAR_URL, 10 | SET_PANEL_MINIMIZED, 11 | SET_PANEL_SIZE, 12 | } from 'actions/ui' 13 | 14 | const defaultState = { 15 | width: 100, 16 | height: 100, 17 | 18 | mainFrameUrl: '', 19 | overlayUrl: null, 20 | } 21 | 22 | const isMainFrameUrl = (sidebarMenu) => 23 | ['', 'acl', 'backups', 'schema', 'cluster', 'connection'].indexOf( 24 | sidebarMenu, 25 | ) >= 0 26 | 27 | export default (state = defaultState, action) => 28 | produce(state, (draft) => { 29 | switch (action.type) { 30 | case SET_PANEL_MINIMIZED: 31 | draft.panelMinimized = action.minimized 32 | break 33 | 34 | case SET_PANEL_SIZE: 35 | draft.panelHeight = action.height 36 | draft.panelWidth = action.width 37 | break 38 | 39 | case CLICK_SIDEBAR_URL: 40 | const url = action.url 41 | if (isMainFrameUrl(url)) { 42 | draft.mainFrameUrl = url 43 | draft.overlayUrl = null 44 | } else { 45 | draft.overlayUrl = draft.overlayUrl === url ? null : url 46 | } 47 | break 48 | 49 | default: 50 | return 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /client/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | const reportWebVitals = (onPerfEntry) => { 7 | if (onPerfEntry && onPerfEntry instanceof Function) { 8 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 9 | getCLS(onPerfEntry) 10 | getFID(onPerfEntry) 11 | getFCP(onPerfEntry) 12 | getLCP(onPerfEntry) 13 | getTTFB(onPerfEntry) 14 | }) 15 | } 16 | } 17 | 18 | export default reportWebVitals 19 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 7 | // allows you to do things like: 8 | // expect(element).toHaveTextContent(/react/i) 9 | // learn more: https://github.com/testing-library/jest-dom 10 | import '@testing-library/jest-dom' 11 | -------------------------------------------------------------------------------- /dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.17.0-alpine 2 | LABEL maintainer="Hypermode " 3 | 4 | RUN apk update && apk --no-cache add \ 5 | make git bash python3 gcc g++ ca-certificates curl 6 | 7 | WORKDIR /app 8 | 9 | CMD /bin/sh 10 | -------------------------------------------------------------------------------- /dev/readme.md: -------------------------------------------------------------------------------- 1 | # Running via container 2 | 3 | ## Requirements 4 | 5 | - Docker 6 | - Docker Compose 7 | 8 | ## Running in background 9 | 10 | ```bash 11 | docker-compose up -d 12 | ``` 13 | 14 | ## Attach to container 15 | 16 | ```bash 17 | docker exec -it ratel_dev_1 bash 18 | ``` 19 | 20 | ## Running the start script 21 | 22 | if you already attached to the container, you can run the start script directly 23 | 24 | ```bash 25 | bash ./dev/run.sh 26 | ``` 27 | 28 | or you can run the start script from outside the container 29 | 30 | ```bash 31 | docker exec -it ratel_dev_1 bash -c "bash ./dev/run.sh" 32 | ``` 33 | 34 | # Issues with Node.js 35 | 36 | To run this project locally you have to use Node.js version 14.x. If you have a different version 37 | installed, you will get errors when running the development server. 38 | 39 | To downgrade Node.js, you can use the Node Version Manager (NVM). If you don't have NVM installed, 40 | you can install it with the following command: 41 | 42 | ```bash 43 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash 44 | ``` 45 | 46 | After installing NVM, restart your terminal or run: 47 | 48 | ```bash 49 | source ~/.bashrc 50 | ``` 51 | 52 | Now, you can install a compatible Node.js version. For example, to install Node.js 14.x, run: 53 | 54 | ```bash 55 | 56 | nvm install 14 57 | ``` 58 | 59 | To switch to the newly installed version: 60 | 61 | ```bash 62 | nvm use 14 63 | ``` 64 | 65 | After downgrading your Node.js version, try running your development server again. The error should 66 | be resolved, and your project should work as expected. 67 | -------------------------------------------------------------------------------- /dev/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Fix files for Windows. Converting the files to unix type. 6 | # This is necessary if you are developing from Windows and will be working in a Linux container. 7 | # Because the file type is not compatible with Linux if it is on a Windows disk. 8 | # find ./* -type d \( -path *node_modules/* -o -path ./.git -o -path */3rdpartystatic/* \) -prune -o -name '*.js*' -print0 | xargs -0 dos2unix 9 | # find ./* -type d \( -path *node_modules/* -o -path ./.git -o -path */3rdpartystatic/* \) -prune -o -name '*.sh*' -print0 | xargs -0 dos2unix 10 | 11 | cd ./client 12 | 13 | npm cache clean --force 14 | 15 | npm install --legacy-peer-deps --no-optional 16 | 17 | # npm audit fix --force 18 | 19 | npm run start 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | ## Please, read the readme file for more information about developing via container. 2 | version: "3.8" 3 | services: 4 | dev: 5 | build: ./dev 6 | volumes: 7 | - ./:/app 8 | - /var/run/docker.sock:/var/run/docker.sock 9 | working_dir: /app 10 | environment: 11 | # HOST: localhost 12 | NODE_ENV: development 13 | command: /bin/sh -c "while sleep 1000; do :; done" 14 | network_mode: "host" 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dgraph-io/ratel 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package main 7 | 8 | import "github.com/dgraph-io/ratel/server" 9 | 10 | func main() { 11 | server.Run() 12 | } 13 | -------------------------------------------------------------------------------- /scripts/build.prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | dir="$(cd "$(printf '%s' "${BASH_SOURCE[0]%/*}")" && pwd)" 6 | rootDir=$(git rev-parse --show-toplevel) 7 | 8 | # cd to the scripts directory 9 | pushd "$dir" >/dev/null 10 | # setting metadata and flags 11 | version="$(grep -i '"version"' <"$rootDir/client/package.json" | awk -F '"' '{print $4}')" 12 | flagUploadToS3=false 13 | buildClientFiles=false 14 | buildServerBinary=false 15 | commitID="$(git rev-parse --short HEAD)" 16 | commitINFO="$(git show --pretty=format:"%h %ad %d" | head -n1)" 17 | 18 | while [ "$1" != "" ]; do 19 | case $1 in 20 | -v | --version) 21 | shift 22 | version=$1 23 | ;; 24 | -u | --upload) 25 | flagUploadToS3=true 26 | ;; 27 | 28 | -c | --client) 29 | buildClientFiles=true 30 | ;; 31 | 32 | -s | --server) 33 | buildServerBinary=true 34 | ;; 35 | esac 36 | 37 | shift 38 | done 39 | 40 | # including functions to build client and server 41 | source ./functions.sh 42 | popd >/dev/null 43 | 44 | # cd to the root folder. 45 | pushd "$rootDir" >/dev/null 46 | 47 | # no flag provided build all 48 | if [ $buildClientFiles = false ] && [ $buildServerBinary = false ]; then 49 | buildClientFiles=true 50 | buildServerBinary=true 51 | fi 52 | 53 | if [ $buildClientFiles = true ]; then 54 | # build client - production flag set to true 55 | buildClient true 56 | fi 57 | 58 | if [ $buildServerBinary = true ]; then 59 | # build server - passing along the production flag and version 60 | buildServer true "$version" 61 | fi 62 | 63 | # uploading to s3 when the flagUploadToS3 flag set to true 64 | if [ $flagUploadToS3 = true ]; then 65 | uploadToS3 66 | fi 67 | popd >/dev/null 68 | 69 | printf "\nDONE\n" 70 | -------------------------------------------------------------------------------- /scripts/provision.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ###### 4 | # Provisioning Script - installs docker, docker-compose, nodejs, and required 5 | # libraries for puppeteer on Ubuntu 20.04 6 | ############ 7 | 8 | # docker install 9 | apt-get install -y apt-transport-https ca-certificates gnupg-agent 10 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 11 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 12 | apt update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io 13 | 14 | # docker-compose 15 | curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 16 | chmod +x /usr/local/bin/docker-compose 17 | 18 | # nodejs 19 | curl -fsSL https://deb.nodesource.com/setup_14.x | bash - 20 | apt-get install -y nodejs 21 | 22 | # puppeteer requirements 23 | apt-get install -y \ 24 | ca-certificates \ 25 | fonts-liberation \ 26 | libappindicator3-1 \ 27 | libasound2 \ 28 | libatk-bridge2.0-0 \ 29 | libatk1.0-0 \ 30 | libc6 \ 31 | libcairo2 \ 32 | libcups2 \ 33 | libdbus-1-3 \ 34 | libexpat1 \ 35 | libfontconfig1 \ 36 | libgbm1 \ 37 | libgcc1 \ 38 | libglib2.0-0 \ 39 | libgtk-3-0 \ 40 | libnspr4 \ 41 | libnss3 \ 42 | libpango-1.0-0 \ 43 | libpangocairo-1.0-0 \ 44 | libstdc++6 \ 45 | libx11-6 \ 46 | libx11-xcb1 \ 47 | libxcb1 \ 48 | libxcomposite1 \ 49 | libxcursor1 \ 50 | libxdamage1 \ 51 | libxext6 \ 52 | libxfixes3 \ 53 | libxi6 \ 54 | libxrandr2 \ 55 | libxrender1 \ 56 | libxss1 \ 57 | libxtst6 \ 58 | lsb-release \ 59 | wget \ 60 | xdg-utils 61 | 62 | # other 63 | apt install make 64 | -------------------------------------------------------------------------------- /server/buffer.go: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package server 7 | 8 | import ( 9 | "bytes" 10 | "errors" 11 | "io" 12 | ) 13 | 14 | // buffer implements io.ReadSeeker for []byte. 15 | type buffer struct { 16 | b *bytes.Buffer 17 | idx int64 18 | } 19 | 20 | func newBuffer(bs []byte) *buffer { 21 | return &buffer{b: bytes.NewBuffer(bs)} 22 | } 23 | 24 | func (b *buffer) Read(bs []byte) (n int, err error) { 25 | if len(bs) == 0 { 26 | return 0, nil 27 | } 28 | if b.idx >= int64(b.b.Len()) { 29 | return 0, io.EOF 30 | } 31 | 32 | n, err = bytes.NewBuffer(b.b.Bytes()[b.idx:]).Read(bs) 33 | b.idx += int64(n) 34 | return n, err 35 | } 36 | 37 | func (b *buffer) Seek(offset int64, whence int) (idx int64, err error) { 38 | var abs int64 39 | switch whence { 40 | case 0: 41 | abs = offset 42 | case 1: 43 | abs = int64(b.idx) + offset 44 | case 2: 45 | abs = int64(b.b.Len()) + offset 46 | default: 47 | return 0, errors.New("buffer.Seek: invalid whence") 48 | } 49 | if abs < 0 { 50 | return 0, errors.New("buffer.Seek: negative position") 51 | } 52 | 53 | b.idx = abs 54 | return abs, nil 55 | } 56 | -------------------------------------------------------------------------------- /server/content.go: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package server 7 | 8 | import ( 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | type content struct { 14 | name string 15 | modTime time.Time 16 | bs []byte 17 | } 18 | 19 | func (c *content) serve(w http.ResponseWriter, r *http.Request) { 20 | http.ServeContent(w, r, c.name, c.modTime, newBuffer(c.bs)) 21 | } 22 | -------------------------------------------------------------------------------- /server/validate.go: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: © Hypermode Inc. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package server 7 | 8 | import ( 9 | "errors" 10 | "net/url" 11 | ) 12 | 13 | var errAddrNil = errors.New("addr is empty") 14 | 15 | func validateAddr(addr string) (string, error) { 16 | if addr == "" { 17 | return "", errAddrNil 18 | } 19 | 20 | addrURL, err := url.Parse(addr) 21 | if err != nil { 22 | return "", err 23 | } 24 | if addrURL.Opaque != "" { 25 | // Maybe the scheme is missing and the url module has parsed the url as 26 | // if it's in the form "scheme:opaque[?query][#fragment]". For example: 27 | // "localhost:8080". 28 | addrURL, err = url.Parse("http://" + addr) 29 | if err != nil { 30 | return "", errors.New("addr should be of the form \"[scheme:]//[userinfo@]host[path]\"") 31 | } 32 | } 33 | if addrURL.Host == "" { 34 | return "", errors.New("host is empty") 35 | } 36 | 37 | addrURL.ForceQuery = false 38 | addrURL.RawQuery = "" 39 | addrURL.Fragment = "" 40 | 41 | return addrURL.String(), nil 42 | } 43 | -------------------------------------------------------------------------------- /test.Dockerfile: -------------------------------------------------------------------------------- 1 | # -*- mode: Dockerfile -*- 2 | # vi: set ft=Dockerfile : 3 | FROM node:14.17.0-buster as test 4 | 5 | # Borrowed from TeamCity Build Task 6 | # ref https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix 7 | RUN apt-get update && \ 8 | apt-get install -y \ 9 | ca-certificates \ 10 | fonts-liberation \ 11 | libappindicator3-1 \ 12 | libasound2 \ 13 | libatk-bridge2.0-0 \ 14 | libatk1.0-0 \ 15 | libc6 \ 16 | libcairo2 \ 17 | libcups2 \ 18 | libdbus-1-3 \ 19 | libexpat1 \ 20 | libfontconfig1 \ 21 | libgbm1 \ 22 | libgcc1 \ 23 | libglib2.0-0 \ 24 | libgtk-3-0 \ 25 | libnspr4 \ 26 | libnss3 \ 27 | libpango-1.0-0 \ 28 | libpangocairo-1.0-0 \ 29 | libstdc++6 \ 30 | libx11-6 \ 31 | libx11-xcb1 \ 32 | libxcb1 \ 33 | libxcomposite1 \ 34 | libxcursor1 \ 35 | libxdamage1 \ 36 | libxext6 \ 37 | libxfixes3 \ 38 | libxi6 \ 39 | libxrandr2 \ 40 | libxrender1 \ 41 | libxss1 \ 42 | libxtst6 \ 43 | lsb-release \ 44 | wget \ 45 | xdg-utils 46 | 47 | COPY . /ratel 48 | RUN groupadd -r dgraph && \ 49 | useradd -r -g dgraph -G dgraph dgraph && \ 50 | mkdir -p /home/dgraph/Downloads /ratel && \ 51 | chown -R dgraph:dgraph /home/dgraph && \ 52 | chown -R dgraph:dgraph /ratel 53 | 54 | # build package manifest layer 55 | WORKDIR /ratel/client 56 | USER dgraph 57 | # install node modules 58 | RUN npm install --legacy-peer-deps --no-optional 59 | --------------------------------------------------------------------------------