├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── release.yaml ├── .gitignore ├── .goreleaser.yml ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── agent ├── adapter │ └── http │ │ ├── acl.go │ │ ├── acl_policy.go │ │ ├── acl_token.go │ │ ├── agent.go │ │ ├── connection.go │ │ ├── errors.go │ │ ├── fallthrough.go │ │ ├── interface.go │ │ ├── middleware │ │ ├── cors.go │ │ └── logging.go │ │ ├── network.go │ │ ├── node.go │ │ ├── spa.go │ │ ├── status.go │ │ └── util.go ├── agent.go ├── banner.go ├── config.go ├── conn │ ├── conn.go │ └── conn_test.go └── ui.go ├── air.conf ├── air.sh ├── api ├── README.md ├── acl.go ├── acl_policy.go ├── acl_token.go ├── agent.go ├── api.go ├── config.go ├── connection.go ├── error.go ├── interface.go ├── network.go └── node.go ├── build ├── Dockerfile.builder └── Dockerfile.linux_amd64 ├── client ├── client.go ├── config.go ├── nic │ ├── controller.go │ ├── controller_test.go │ ├── nic.go │ └── util.go ├── state │ ├── boltdb │ │ └── boltdb.go │ ├── inmem │ │ └── inmem.go │ └── state.go └── util.go ├── command ├── acl.go ├── acl_bootstrap.go ├── acl_policy.go ├── acl_policy_apply.go ├── acl_policy_delete.go ├── acl_policy_info.go ├── acl_policy_list.go ├── acl_token.go ├── acl_token_create.go ├── acl_token_delete.go ├── acl_token_info.go ├── acl_token_list.go ├── acl_token_self.go ├── acl_token_update.go ├── agent.go ├── agent_info.go ├── command.go ├── connection.go ├── connection_create.go ├── connection_delete.go ├── connection_list.go ├── connection_update.go ├── connection_update_rules.go ├── interface.go ├── interface_list.go ├── interface_update.go ├── network.go ├── network_create.go ├── network_delete.go ├── network_info.go ├── network_list.go ├── node.go ├── node_info.go ├── node_join.go ├── node_leave.go ├── node_list.go ├── node_status.go ├── ui.go ├── util.go └── version.go ├── docs ├── .nojekyll ├── README.md ├── _404.md ├── _coverpage.md ├── _navbar.md ├── _sidebar.md ├── api │ ├── README.md │ ├── acl-policies.md │ ├── acl-tokens.md │ ├── connections.md │ ├── interfaces.md │ ├── networks.md │ ├── nodes.md │ ├── status.md │ └── ui.md ├── assets │ └── logos │ │ └── dragopher.svg ├── contributing.md ├── css │ └── custom.css ├── docs │ ├── README.md │ ├── commands │ │ ├── README.md │ │ ├── acl │ │ │ ├── README.md │ │ │ ├── bootstrap.md │ │ │ ├── policy-apply.md │ │ │ ├── policy-delete.md │ │ │ ├── policy-info.md │ │ │ ├── policy-list.md │ │ │ ├── token-create.md │ │ │ ├── token-delete.md │ │ │ ├── token-info.md │ │ │ ├── token-list.md │ │ │ ├── token-self.md │ │ │ └── token-update.md │ │ ├── agent-info.md │ │ ├── agent.md │ │ ├── connection │ │ │ ├── create.md │ │ │ ├── list.md │ │ │ └── update.md │ │ ├── interface │ │ │ ├── list.md │ │ │ └── update.md │ │ ├── network │ │ │ ├── create.md │ │ │ ├── delete.md │ │ │ ├── info.md │ │ │ └── list.md │ │ ├── node │ │ │ ├── info.md │ │ │ ├── join.md │ │ │ ├── leave.md │ │ │ ├── list.md │ │ │ └── status.md │ │ └── ui.md │ ├── configuration │ │ ├── README.md │ │ ├── acl.md │ │ ├── client.md │ │ └── server.md │ ├── installing │ │ ├── README.md │ │ └── quickstart.md │ ├── internals │ │ ├── README.md │ │ └── architecture.md │ └── overview.md ├── faq.md ├── favicon.ico ├── index.html ├── js │ └── darklight-plugin.js └── license.md ├── drago ├── acl.go ├── acl_policy.go ├── acl_token.go ├── auth │ ├── auth.go │ ├── policy.go │ ├── rule.go │ └── token.go ├── config.go ├── connection.go ├── interface.go ├── mock │ └── mock.go ├── network.go ├── node.go ├── server.go ├── state │ ├── etcd │ │ ├── acl_policy.go │ │ ├── acl_state.go │ │ ├── acl_token.go │ │ ├── connection.go │ │ ├── etcd.go │ │ ├── interface.go │ │ ├── network.go │ │ └── node.go │ ├── inmem │ │ ├── acl_policy.go │ │ ├── acl_state.go │ │ ├── acl_token.go │ │ ├── connection.go │ │ ├── inmem.go │ │ ├── interface.go │ │ ├── network.go │ │ └── node.go │ └── state.go ├── status.go ├── structs │ ├── acl.go │ ├── acl_policy.go │ ├── acl_token.go │ ├── agent.go │ ├── config │ │ ├── acl.go │ │ └── etcd.go │ ├── connection.go │ ├── errors.go │ ├── interface.go │ ├── network.go │ ├── node.go │ ├── status.go │ └── structs.go └── test │ ├── acl_test.go │ ├── interface_test.go │ └── node_test.go ├── dragopher.svg ├── example ├── client1.hcl ├── client2.hcl └── server.hcl ├── go.mod ├── go.sum ├── init ├── README.md └── systemd │ └── drago.service ├── main.go ├── pkg ├── acl │ ├── README.md │ ├── acl.go │ ├── acl_test.go │ ├── config.go │ ├── errors.go │ ├── logger.go │ ├── model.go │ ├── policy.go │ ├── resolver.go │ └── token.go ├── cli │ ├── cli.go │ ├── command.go │ ├── command_mock.go │ ├── config.go │ ├── help.go │ ├── router.go │ └── ui.go ├── concurrent │ └── map.go ├── http │ ├── config.go │ ├── handler.go │ ├── http.go │ └── middleware.go ├── log │ ├── log.go │ ├── logrus │ │ └── logrus.go │ ├── simple │ │ └── simple.go │ └── zap │ │ └── zap.go ├── radix │ ├── README.md │ ├── node.go │ ├── radix.go │ └── radix_test.go ├── rpc │ ├── codec.go │ ├── config.go │ └── rpc.go ├── string │ └── string.go ├── util │ └── util.go ├── uuid │ └── uuid.go └── validator │ └── validator.go ├── plugin ├── admission │ └── admission.go ├── lease │ └── lease.go ├── mesh │ └── mesh.go ├── notification │ └── notification.go └── plugin.go ├── ui ├── .babelrc ├── .eslintrc ├── .prettierrc ├── .vscode │ └── settings.json ├── README.md ├── build │ └── .keep ├── config │ ├── rewired │ │ └── index.js │ └── webpack │ │ ├── eslint.js │ │ └── resolve.js ├── jsconfig.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── stylesheet.css ├── src │ ├── assets │ │ ├── fonts │ │ │ ├── Lato-Black.woff │ │ │ ├── Lato-Black.woff2 │ │ │ ├── Lato-BlackItalic.woff │ │ │ ├── Lato-BlackItalic.woff2 │ │ │ ├── Lato-Bold.woff │ │ │ ├── Lato-Bold.woff2 │ │ │ ├── Lato-BoldItalic.woff │ │ │ ├── Lato-BoldItalic.woff2 │ │ │ ├── Lato-Hairline.woff │ │ │ ├── Lato-Hairline.woff2 │ │ │ ├── Lato-HairlineItalic.woff │ │ │ ├── Lato-HairlineItalic.woff2 │ │ │ ├── Lato-Italic.woff │ │ │ ├── Lato-Italic.woff2 │ │ │ ├── Lato-Light.woff │ │ │ ├── Lato-Light.woff2 │ │ │ ├── Lato-LightItalic.woff │ │ │ ├── Lato-LightItalic.woff2 │ │ │ ├── Lato-Regular.woff │ │ │ ├── Lato-Regular.woff2 │ │ │ ├── Montserrat-Black.woff │ │ │ ├── Montserrat-Black.woff2 │ │ │ ├── Montserrat-BlackItalic.woff │ │ │ ├── Montserrat-BlackItalic.woff2 │ │ │ ├── Montserrat-Bold.woff │ │ │ ├── Montserrat-Bold.woff2 │ │ │ ├── Montserrat-BoldItalic.woff │ │ │ ├── Montserrat-BoldItalic.woff2 │ │ │ ├── Montserrat-ExtraBold.woff │ │ │ ├── Montserrat-ExtraBold.woff2 │ │ │ ├── Montserrat-ExtraBoldItalic.woff │ │ │ ├── Montserrat-ExtraBoldItalic.woff2 │ │ │ ├── Montserrat-ExtraLight.woff │ │ │ ├── Montserrat-ExtraLight.woff2 │ │ │ ├── Montserrat-ExtraLightItalic.woff │ │ │ ├── Montserrat-ExtraLightItalic.woff2 │ │ │ ├── Montserrat-Italic.woff │ │ │ ├── Montserrat-Italic.woff2 │ │ │ ├── Montserrat-Light.woff │ │ │ ├── Montserrat-Light.woff2 │ │ │ ├── Montserrat-LightItalic.woff │ │ │ ├── Montserrat-LightItalic.woff2 │ │ │ ├── Montserrat-Medium.woff │ │ │ ├── Montserrat-Medium.woff2 │ │ │ ├── Montserrat-MediumItalic.woff │ │ │ ├── Montserrat-MediumItalic.woff2 │ │ │ ├── Montserrat-Regular.woff │ │ │ ├── Montserrat-Regular.woff2 │ │ │ ├── Montserrat-SemiBold.woff │ │ │ ├── Montserrat-SemiBold.woff2 │ │ │ ├── Montserrat-SemiBoldItalic.woff │ │ │ ├── Montserrat-SemiBoldItalic.woff2 │ │ │ ├── Montserrat-Thin.woff │ │ │ ├── Montserrat-Thin.woff2 │ │ │ ├── Montserrat-ThinItalic.woff │ │ │ ├── Montserrat-ThinItalic.woff2 │ │ │ ├── Raleway-Black.woff │ │ │ ├── Raleway-Black.woff2 │ │ │ ├── Raleway-BlackItalic.woff │ │ │ ├── Raleway-BlackItalic.woff2 │ │ │ ├── Raleway-Bold.woff │ │ │ ├── Raleway-Bold.woff2 │ │ │ ├── Raleway-BoldItalic.woff │ │ │ ├── Raleway-BoldItalic.woff2 │ │ │ ├── Raleway-ExtraBold.woff │ │ │ ├── Raleway-ExtraBold.woff2 │ │ │ ├── Raleway-ExtraBoldItalic.woff │ │ │ ├── Raleway-ExtraBoldItalic.woff2 │ │ │ ├── Raleway-ExtraLight.woff │ │ │ ├── Raleway-ExtraLight.woff2 │ │ │ ├── Raleway-ExtraLightItalic.woff │ │ │ ├── Raleway-ExtraLightItalic.woff2 │ │ │ ├── Raleway-Italic.woff │ │ │ ├── Raleway-Italic.woff2 │ │ │ ├── Raleway-Light.woff │ │ │ ├── Raleway-Light.woff2 │ │ │ ├── Raleway-LightItalic.woff │ │ │ ├── Raleway-LightItalic.woff2 │ │ │ ├── Raleway-Medium.woff │ │ │ ├── Raleway-Medium.woff2 │ │ │ ├── Raleway-MediumItalic.woff │ │ │ ├── Raleway-MediumItalic.woff2 │ │ │ ├── Raleway-Regular.woff │ │ │ ├── Raleway-Regular.woff2 │ │ │ ├── Raleway-SemiBold.woff │ │ │ ├── Raleway-SemiBold.woff2 │ │ │ ├── Raleway-SemiBoldItalic.woff │ │ │ ├── Raleway-SemiBoldItalic.woff2 │ │ │ ├── Raleway-Thin.woff │ │ │ ├── Raleway-Thin.woff2 │ │ │ ├── Raleway-ThinItalic.woff │ │ │ ├── Raleway-ThinItalic.woff2 │ │ │ ├── Roboto-Black.woff │ │ │ ├── Roboto-Black.woff2 │ │ │ ├── Roboto-BlackItalic.woff │ │ │ ├── Roboto-BlackItalic.woff2 │ │ │ ├── Roboto-Bold.woff │ │ │ ├── Roboto-Bold.woff2 │ │ │ ├── Roboto-BoldItalic.woff │ │ │ ├── Roboto-BoldItalic.woff2 │ │ │ ├── Roboto-Italic.woff │ │ │ ├── Roboto-Italic.woff2 │ │ │ ├── Roboto-Light.woff │ │ │ ├── Roboto-Light.woff2 │ │ │ ├── Roboto-LightItalic.woff │ │ │ ├── Roboto-LightItalic.woff2 │ │ │ ├── Roboto-Medium.woff │ │ │ ├── Roboto-Medium.woff2 │ │ │ ├── Roboto-MediumItalic.woff │ │ │ ├── Roboto-MediumItalic.woff2 │ │ │ ├── Roboto-Regular.woff │ │ │ ├── Roboto-Regular.woff2 │ │ │ ├── Roboto-Thin.woff │ │ │ ├── Roboto-Thin.woff2 │ │ │ ├── Roboto-ThinItalic.woff │ │ │ ├── Roboto-ThinItalic.woff2 │ │ │ └── index.css │ │ ├── icons │ │ │ ├── back.svg │ │ │ ├── bell.svg │ │ │ ├── buoy.svg │ │ │ ├── connection-small.svg │ │ │ ├── connection.svg │ │ │ ├── dots.svg │ │ │ ├── down.svg │ │ │ ├── error.svg │ │ │ ├── external-link.svg │ │ │ ├── host.svg │ │ │ ├── index.js │ │ │ ├── interface.svg │ │ │ ├── key.svg │ │ │ ├── label.svg │ │ │ ├── leave.svg │ │ │ ├── left.svg │ │ │ ├── link.svg │ │ │ ├── logo.svg │ │ │ ├── network.svg │ │ │ ├── plus.svg │ │ │ ├── refresh.svg │ │ │ ├── right.svg │ │ │ ├── search.svg │ │ │ ├── success.svg │ │ │ ├── times.svg │ │ │ ├── trash.svg │ │ │ ├── unknown.svg │ │ │ ├── up.svg │ │ │ └── warning.svg │ │ ├── illustrations │ │ │ ├── empty.svg │ │ │ ├── error.svg │ │ │ ├── index.js │ │ │ ├── not-found.svg │ │ │ └── unauthorized.svg │ │ └── index.js │ ├── components │ │ ├── avatar │ │ │ └── index.js │ │ ├── back-link │ │ │ └── index.js │ │ ├── box │ │ │ └── index.js │ │ ├── button │ │ │ └── index.js │ │ ├── collapse │ │ │ └── index.js │ │ ├── confirmation-dialog │ │ │ ├── dialog.js │ │ │ └── index.js │ │ ├── empty-state │ │ │ └── index.js │ │ ├── error-state │ │ │ └── index.js │ │ ├── flex │ │ │ └── index.js │ │ ├── headers │ │ │ └── index.js │ │ ├── icon-button │ │ │ └── index.js │ │ ├── icon │ │ │ └── index.js │ │ ├── inputs │ │ │ ├── number-input │ │ │ │ └── index.js │ │ │ ├── search-input │ │ │ │ └── index.js │ │ │ ├── select-input │ │ │ │ └── index.js │ │ │ ├── tags-input │ │ │ │ └── index.js │ │ │ └── text-input │ │ │ │ └── index.js │ │ ├── key-value │ │ │ └── index.js │ │ ├── link │ │ │ └── index.js │ │ ├── list │ │ │ └── index.js │ │ ├── nav │ │ │ └── index.js │ │ ├── network-select-input │ │ │ └── index.js │ │ ├── node-select-input │ │ │ └── index.js │ │ ├── popover │ │ │ └── index.js │ │ ├── separator │ │ │ └── index.js │ │ ├── spinner │ │ │ ├── dragon.js │ │ │ ├── index.js │ │ │ └── jellyfish.js │ │ ├── steps │ │ │ └── index.js │ │ ├── text │ │ │ └── index.js │ │ ├── toast │ │ │ └── index.js │ │ ├── tooltip │ │ │ └── index.js │ │ └── unauthorized-state │ │ │ └── index.js │ ├── containers │ │ ├── footer │ │ │ └── index.js │ │ ├── header │ │ │ └── index.js │ │ └── side-nav │ │ │ ├── brand.js │ │ │ ├── index.js │ │ │ └── styled.js │ ├── environment.js │ ├── graphql │ │ ├── apollo-provider.js │ │ ├── local-state.js │ │ ├── mutations │ │ │ └── index.js │ │ └── queries │ │ │ └── index.js │ ├── index.js │ ├── mock │ │ ├── commands.js │ │ ├── factories.js │ │ ├── identity.js │ │ ├── index.js │ │ ├── models.js │ │ ├── queries.js │ │ ├── routes.js │ │ ├── seeds.js │ │ └── util.js │ ├── modals │ │ ├── admit-node │ │ │ ├── index.js │ │ │ └── node-card.js │ │ ├── connect-peer │ │ │ ├── index.js │ │ │ └── peer-card.js │ │ └── join-network │ │ │ ├── index.js │ │ │ └── network-card.js │ ├── serviceWorker.js │ ├── styles │ │ ├── index.js │ │ └── themes │ │ │ └── light.js │ ├── utils │ │ ├── formik-utils.js │ │ ├── hocs │ │ │ ├── index.js │ │ │ └── with-validity-indicator.js │ │ ├── toast-provider.js │ │ └── use-localstorage.js │ └── views │ │ ├── app │ │ └── index.js │ │ ├── clients │ │ ├── details │ │ │ ├── connection-card.js │ │ │ ├── index.js │ │ │ ├── interface-card.js │ │ │ └── network-card.js │ │ ├── index.js │ │ └── list │ │ │ ├── client-card.js │ │ │ └── index.js │ │ ├── home │ │ └── index.js │ │ ├── networks │ │ ├── details │ │ │ ├── graph │ │ │ │ ├── graph.js │ │ │ │ ├── index.js │ │ │ │ ├── interface-card.js │ │ │ │ └── link-card.js │ │ │ ├── index.js │ │ │ ├── peer-card.js │ │ │ └── topology.js │ │ ├── index.js │ │ ├── list │ │ │ ├── index.js │ │ │ └── network-card.js │ │ └── new │ │ │ └── index.js │ │ ├── not-found │ │ └── index.js │ │ └── settings │ │ ├── index.js │ │ └── tokens │ │ └── index.js ├── yarn-error.log └── yarn.lock └── version └── version.go /.dockerignore: -------------------------------------------------------------------------------- 1 | ui/node_modules -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Drago 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout branch 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Setup Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.16 23 | 24 | - name: Setup Node 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: '14' 28 | 29 | - name: Run GoReleaser 30 | uses: goreleaser/goreleaser-action@v2 31 | with: 32 | version: latest 33 | args: release --rm-dist 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/go 2 | # Edit at https://www.gitignore.io/?templates=go 3 | 4 | ### Go ### 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | bin/ 22 | 23 | ### Go Patch ### 24 | vendor/ 25 | Godeps/ 26 | 27 | # End of https://www.gitignore.io/api/go 28 | ui/node_modules/* 29 | ui/.eslintcache 30 | ui/build/* 31 | vendor/* 32 | 33 | !*.keep 34 | 35 | tmp 36 | dist/ 37 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | - go generate ./... 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | - GO111MODULE=on 9 | goos: 10 | - linux 11 | goarch: 12 | - amd64 13 | - arm 14 | - arm64 15 | ldflags: 16 | - -s -w -extldflags "-static" 17 | - -s -d -X version.Version={{ .Version }} 18 | archives: 19 | - replacements: 20 | darwin: Darwin 21 | linux: Linux 22 | windows: Windows 23 | 386: i386 24 | amd64: x86_64 25 | name_template: "{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 26 | checksum: 27 | name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt" 28 | snapshot: 29 | name_template: "{{ .Tag }}-next" 30 | release: 31 | name_template: "v{{ .Version }}" 32 | changelog: 33 | sort: asc 34 | filters: 35 | exclude: 36 | - '^docs:' 37 | - '^test:' 38 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug Drago Development Mode", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "${workspaceFolder}", 13 | "args": ["agent", "--dev"] 14 | }, 15 | { 16 | "name": "Debug Drago Server", 17 | "type": "go", 18 | "request": "launch", 19 | "mode": "debug", 20 | "program": "${workspaceFolder}", 21 | "args": ["agent", "--config=./dist/server.hcl"] 22 | }, 23 | { 24 | "name": "Debug Drago Client", 25 | "type": "go", 26 | "request": "launch", 27 | "mode": "debug", 28 | "program": "${workspaceFolder}", 29 | "args": ["agent", "--config=./dist/client1.hcl"] 30 | }, 31 | ] 32 | } -------------------------------------------------------------------------------- /agent/adapter/http/acl.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/seashell/drago/agent/conn" 7 | structs "github.com/seashell/drago/drago/structs" 8 | ) 9 | 10 | // ACLHandler : 11 | type ACLHandler struct { 12 | rpcConn conn.RPCConnection 13 | } 14 | 15 | // NewACLHandler : 16 | func NewACLHandler(conn conn.RPCConnection) *ACLHandler { 17 | return &ACLHandler{ 18 | rpcConn: conn, 19 | } 20 | } 21 | 22 | // Handle : 23 | func (h *ACLHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { 24 | 25 | params := parsePathParams(req) 26 | if len(params) > 1 { 27 | return nil, NewCodedError(404, ErrNotFound) 28 | } 29 | 30 | switch params[0] { 31 | case "bootstrap": 32 | return h.handleBootstrap(rw, req) 33 | default: 34 | return nil, NewCodedError(404, "Not found") 35 | } 36 | 37 | } 38 | 39 | func (h *ACLHandler) handleBootstrap(rw http.ResponseWriter, req *http.Request) (interface{}, error) { 40 | 41 | if req.Method != "POST" { 42 | return nil, NewCodedError(405, ErrMethodNotAllowed) 43 | } 44 | 45 | args := structs.ACLBootstrapRequest{ 46 | WriteRequest: parseWriteRequestOptions(req), 47 | } 48 | 49 | var out structs.ACLTokenUpsertResponse 50 | if err := h.rpcConn.Call("ACL.BootstrapACL", &args, &out); err != nil { 51 | return nil, parseError(err) 52 | } 53 | 54 | return out.ACLToken, nil 55 | } 56 | -------------------------------------------------------------------------------- /agent/adapter/http/agent.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | 6 | conn "github.com/seashell/drago/agent/conn" 7 | structs "github.com/seashell/drago/drago/structs" 8 | ) 9 | 10 | type AgentAdapter interface { 11 | Config() map[string]interface{} 12 | Stats() map[string]map[string]string 13 | } 14 | 15 | // AgentHandler provides an API for interacting with an Agent 16 | // in runtime, getting stats and setting configs. 17 | type AgentHandler struct { 18 | agent AgentAdapter 19 | } 20 | 21 | // NewAgentHandler : 22 | func NewAgentHandler(conn conn.RPCConnection, ag AgentAdapter) *AgentHandler { 23 | return &AgentHandler{ 24 | agent: ag, 25 | } 26 | } 27 | 28 | // Handle : 29 | func (h *AgentHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { 30 | 31 | params := parsePathParams(req) 32 | if len(params) > 1 { 33 | return nil, NewCodedError(404, ErrNotFound) 34 | } 35 | 36 | id := params[0] 37 | 38 | switch req.Method { 39 | case "GET": 40 | return h.handleGet(rw, req, id) 41 | default: 42 | return nil, NewCodedError(405, ErrMethodNotAllowed) 43 | } 44 | } 45 | 46 | func (h *AgentHandler) handleGet(rw http.ResponseWriter, req *http.Request, id string) (interface{}, error) { 47 | 48 | if id != "self" { 49 | return nil, NewCodedError(404, ErrNotFound) 50 | } 51 | 52 | self := &structs.Agent{ 53 | Config: map[string]interface{}{}, 54 | Stats: h.agent.Stats(), 55 | } 56 | 57 | return self, nil 58 | } 59 | -------------------------------------------------------------------------------- /agent/adapter/http/errors.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | ErrNotFound = "Not found" 9 | ErrInternal = "Internal error" 10 | ErrBadRequest = "Bad request" 11 | ErrMethodNotAllowed = "Method not allowed" 12 | ) 13 | 14 | // CodedError represents an error generated by an HTTP handler. 15 | // Besides implementing the standard error interface, it also 16 | // implements the http.Error interface, which includes a method 17 | // for obtaining the HTTP status code associated with the error. 18 | type CodedError struct { 19 | // Code is an HTTP status code associated with the error. 20 | code int 21 | 22 | // Message is a human-readable message that describes the error. 23 | Message string 24 | } 25 | 26 | // NewCodedError : creates a new HTTPError object. 27 | func NewCodedError(code int, s string, extra ...interface{}) CodedError { 28 | msg := s 29 | // TODO: only append extra to msg if DEBUG enabled 30 | for _, v := range extra { 31 | msg = fmt.Sprintf("%s : %v", msg, v) 32 | } 33 | return CodedError{code, msg} 34 | } 35 | 36 | // Error returns a string representation for the Error type. 37 | func (e CodedError) Error() string { 38 | return fmt.Sprintf("%s", e.Message) 39 | } 40 | 41 | // Code returns the HTTP status code associated with the error. 42 | func (e CodedError) Code() int { 43 | return e.code 44 | } 45 | -------------------------------------------------------------------------------- /agent/adapter/http/fallthrough.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // FallthroughHandler : 8 | type FallthroughHandler struct { 9 | redirectTo string 10 | } 11 | 12 | // NewFallthroughHandler : 13 | func NewFallthroughHandler(to string) *FallthroughHandler { 14 | return &FallthroughHandler{redirectTo: to} 15 | } 16 | 17 | // Handle : 18 | func (a *FallthroughHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { 19 | switch req.Method { 20 | case "GET": 21 | return a.handleGet(rw, req) 22 | default: 23 | return nil, NewCodedError(405, ErrMethodNotAllowed) 24 | } 25 | } 26 | 27 | func (a *FallthroughHandler) handleGet(rw http.ResponseWriter, req *http.Request) (interface{}, error) { 28 | if req.URL.Path == "/" { 29 | http.Redirect(rw, req, a.redirectTo, 307) 30 | } else { 31 | return nil, NewCodedError(404, ErrNotFound) 32 | } 33 | return nil, nil 34 | } 35 | -------------------------------------------------------------------------------- /agent/adapter/http/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // CORS : 8 | func CORS() func(http.HandlerFunc) http.HandlerFunc { 9 | m := func(next http.HandlerFunc) http.HandlerFunc { 10 | return func(rw http.ResponseWriter, req *http.Request) { 11 | rw.Header().Set("Access-Control-Allow-Origin", "*") 12 | rw.Header().Set("Access-Control-Allow-Methods", "*") 13 | rw.Header().Set("Access-Control-Allow-Headers", "Origin, Accept, Referer, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") 14 | if req.Method == "OPTIONS" { 15 | rw.WriteHeader(http.StatusOK) 16 | return 17 | } 18 | next(rw, req) 19 | } 20 | } 21 | return m 22 | } 23 | -------------------------------------------------------------------------------- /agent/adapter/http/middleware/logging.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "net" 7 | "net/http" 8 | 9 | "time" 10 | 11 | log "github.com/seashell/drago/pkg/log" 12 | ) 13 | 14 | // LoggingResponseWriter : 15 | type LoggingResponseWriter struct { 16 | http.ResponseWriter 17 | code int 18 | } 19 | 20 | // NewLoggingResponseWriter : 21 | func NewLoggingResponseWriter(rw http.ResponseWriter) *LoggingResponseWriter { 22 | return &LoggingResponseWriter{rw, http.StatusOK} 23 | } 24 | 25 | // WriteHeader : 26 | func (lrw *LoggingResponseWriter) WriteHeader(code int) { 27 | lrw.code = code 28 | lrw.ResponseWriter.WriteHeader(code) 29 | } 30 | 31 | // Hijack : 32 | func (lrw *LoggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 33 | h, ok := lrw.ResponseWriter.(http.Hijacker) 34 | if !ok { 35 | return nil, nil, errors.New("hijacking not supported") 36 | } 37 | return h.Hijack() 38 | } 39 | 40 | // Logging : 41 | func Logging(logger log.Logger) func(http.HandlerFunc) http.HandlerFunc { 42 | 43 | m := func(next http.HandlerFunc) http.HandlerFunc { 44 | 45 | return func(rw http.ResponseWriter, req *http.Request) { 46 | 47 | start := time.Now() 48 | url := req.RequestURI 49 | 50 | logger.Debugf("Request received (remote=%s, method=%s, path=%s)", req.RemoteAddr, req.Method, url) 51 | 52 | var status int 53 | defer func() { 54 | logger.Debugf("Request completed with status %d (method=%s, path=%s, duration=%s)", status, req.Method, url, time.Now().Sub(start)) 55 | }() 56 | 57 | lrw := NewLoggingResponseWriter(rw) 58 | next(lrw, req) 59 | status = lrw.code 60 | } 61 | } 62 | 63 | return m 64 | } 65 | -------------------------------------------------------------------------------- /agent/adapter/http/spa.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | // SinglePageApplicationHandler : 9 | type SinglePageApplicationHandler struct { 10 | fsHandler http.Handler 11 | placeholder string 12 | } 13 | 14 | // NewSinglePageApplicationHandler : Create a new SPA handler that serves static files 15 | // from a http.FileSystem passed as argument. If the latter is nil, serve a placeholder string. 16 | func NewSinglePageApplicationHandler(fs http.FileSystem, s string) *SinglePageApplicationHandler { 17 | 18 | handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 19 | rw.Write([]byte(s)) 20 | }) 21 | 22 | if fs != nil { 23 | handler = http.FileServer(fs).ServeHTTP 24 | } 25 | 26 | return &SinglePageApplicationHandler{ 27 | placeholder: s, 28 | fsHandler: handler, 29 | } 30 | } 31 | 32 | func (h *SinglePageApplicationHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { 33 | 34 | path := req.URL.Path 35 | 36 | if path == "" || strings.HasPrefix(path, "/static") { 37 | h.fsHandler.ServeHTTP(rw, req) 38 | } else if path == "/" { 39 | h.fsHandler.ServeHTTP(rw, req) 40 | } else { 41 | req.URL.Path = "/" 42 | h.fsHandler.ServeHTTP(rw, req) 43 | } 44 | 45 | return nil, nil 46 | } 47 | -------------------------------------------------------------------------------- /agent/adapter/http/status.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/seashell/drago/agent/conn" 7 | "github.com/seashell/drago/drago/structs" 8 | ) 9 | 10 | // StatusHandler is used to check on server status 11 | type StatusHandler struct { 12 | rpcConn conn.RPCConnection 13 | } 14 | 15 | // NewStatusHandler : 16 | func NewStatusHandler(conn conn.RPCConnection) *StatusHandler { 17 | return &StatusHandler{ 18 | rpcConn: conn, 19 | } 20 | } 21 | 22 | // Handle : 23 | func (h *StatusHandler) Handle(rw http.ResponseWriter, req *http.Request) (interface{}, error) { 24 | switch req.Method { 25 | case "GET": 26 | return h.handleGet(rw, req) 27 | default: 28 | return nil, NewCodedError(405, ErrMethodNotAllowed) 29 | } 30 | } 31 | 32 | func (h *StatusHandler) handleGet(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 33 | 34 | var args structs.GenericRequest 35 | 36 | var out structs.GenericResponse 37 | if err := h.rpcConn.Call("Status.Ping", &args, &out); err != nil { 38 | return nil, err 39 | } 40 | 41 | return out, nil 42 | } 43 | -------------------------------------------------------------------------------- /agent/banner.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/seashell/drago/version" 7 | ) 8 | 9 | // Banner is a banner to be displayed when the Drago 10 | // agent is started 11 | var Banner = fmt.Sprintf(` 12 | ====|===================> 13 | ___ ____ ____ ____ ____ 14 | | \ |__/ |__| | __ | | 15 | |__/ | \ | | |__] |__| 16 | 17 | {{ .AnsiColor.Cyan }}%s{{ .AnsiColor.Default }} 18 | <===================|==== 19 | 20 | `, version.GetVersion().VersionNumber()) 21 | -------------------------------------------------------------------------------- /agent/conn/conn_test.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestCreateConn(t *testing.T) { 9 | 10 | conn := NewRPCConnection("127.0.0.1:8081", nil) 11 | 12 | if err := conn.Call("TestMethod", struct{}{}, nil); err != nil { 13 | fmt.Println(err) 14 | } 15 | 16 | t.Log("success!") 17 | 18 | } 19 | -------------------------------------------------------------------------------- /agent/ui.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | const uiStubHTML = ` 4 | 5 |
 6 | ====|===================>
 7 | ___  ____ ____ ____ ____ 
 8 | |  \ |__/ |__| | __ |  | 
 9 | |__/ |  \ |  | |__] |__| 
10 | 		   
11 | <===================|====
12 | 
13 | 14 |

If you're seeing this message, it means that Drago's web UI was not built properly.

15 |

In order to build it, run go generate from the project root directory.

16 | 17 | 18 | ` 19 | -------------------------------------------------------------------------------- /air.conf: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/air/drago ." 11 | # Binary file yields from `cmd`. 12 | bin = "tmp/air/drago agent" 13 | # Customize binary. 14 | full_bin = "APP_ENV=dev APP_USER=air ./tmp/air/drago agent --dev" 15 | # Watch these filename extensions. 16 | include_ext = ["go", "tpl", "tmpl", "html"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "ui", "data"] 19 | # Watch these directories if you specified. 20 | include_dir = [] 21 | # Exclude files. 22 | exclude_file = [] 23 | # This log file places in your tmp_dir. 24 | log = "./tmp/air.log" 25 | # It's not necessary to trigger build each time file changes if it's too frequent. 26 | delay = 500 # ms 27 | # Stop running old binary when build errors occur. 28 | stop_on_error = true 29 | # Send Interrupt signal before killing process (windows does not support this feature) 30 | send_interrupt = false 31 | # Delay after sending Interrupt signal 32 | kill_delay = 500 # ms 33 | 34 | [log] 35 | # Show log time 36 | time = false 37 | 38 | [color] 39 | # Customize each part's color. If no color found, use the raw app log. 40 | main = "magenta" 41 | watcher = "cyan" 42 | build = "yellow" 43 | runner = "green" 44 | 45 | [misc] 46 | # Delete tmp directory on exit 47 | clean_on_exit = true -------------------------------------------------------------------------------- /air.sh: -------------------------------------------------------------------------------- 1 | go run github.com/cosmtrek/air -c "./air.conf" -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | ## Drago API Client 2 | 3 | This directory contains the `api` package which aims at providing programmatic access to Drago's HTTP API. 4 | 5 | ### Documentation 6 | 7 | ... 8 | 9 | ### Usage 10 | 11 | ```go 12 | package main 13 | 14 | import "github.com/seashell/drago/api" 15 | 16 | func main() { 17 | // Get a new client 18 | client, err := api.NewClient(api.DefaultConfig()) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | // Get a handle to the networks API 24 | networks := client.Networks() 25 | 26 | // Create a new network 27 | n := &api.Network{ 28 | Name: "my-new-network", 29 | IPAddressRange: "10.1.1.0/24" 30 | } 31 | 32 | id, err := networks.Create(context.Background(), n) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | ... 38 | } 39 | ``` 40 | 41 | To run this example, start a Drago server: 42 | 43 | ``` 44 | drago agent --server 45 | ``` 46 | 47 | Copy the code above into a file such as `main.go`, and run it. 48 | 49 | After running the code, you can also view the values in the Drago UI on your local machine at http://localhost:8080/ui/ 50 | -------------------------------------------------------------------------------- /api/acl.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/seashell/drago/drago/structs" 7 | ) 8 | 9 | const ( 10 | aclPath = "/api/acl" 11 | ) 12 | 13 | // ACL is a handle to the ACL API 14 | type ACL struct { 15 | client *Client 16 | } 17 | 18 | // Networks returns a handle on the networks endpoints. 19 | func (c *Client) ACL() *ACL { 20 | return &ACL{client: c} 21 | } 22 | 23 | // Boostrap : 24 | func (a *ACL) Bootstrap() (*structs.ACLToken, error) { 25 | 26 | var token structs.ACLToken 27 | err := a.client.createResource(path.Join(aclPath, "bootstrap"), nil, &token) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return &token, nil 33 | } 34 | -------------------------------------------------------------------------------- /api/acl_policy.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/seashell/drago/drago/structs" 7 | ) 8 | 9 | const ( 10 | aclPoliciesPath = "/api/acl/policies" 11 | ) 12 | 13 | // ACLPolicies is a handle to the ACL policies API 14 | type ACLPolicies struct { 15 | client *Client 16 | } 17 | 18 | // ACLPolicies returns a handle on the ACL policies endpoints. 19 | func (c *Client) ACLPolicies() *ACLPolicies { 20 | return &ACLPolicies{client: c} 21 | } 22 | 23 | // Create : 24 | func (p *ACLPolicies) Upsert(policy *structs.ACLPolicy) (*structs.ACLPolicy, error) { 25 | 26 | var rcvPolicy *structs.ACLPolicy 27 | err := p.client.createResource(aclPoliciesPath, policy, &rcvPolicy) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return policy, nil 33 | } 34 | 35 | // Delete : 36 | func (p *ACLPolicies) Delete(name string) error { 37 | 38 | err := p.client.deleteResource(name, aclPoliciesPath, nil) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // Get : 47 | func (p *ACLPolicies) Get(name string) (*structs.ACLPolicy, error) { 48 | 49 | out := &structs.ACLPolicy{} 50 | err := p.client.getResource(aclPoliciesPath, name, out) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return out, nil 56 | } 57 | 58 | // List : 59 | func (p *ACLPolicies) List() ([]*structs.ACLPolicyListStub, error) { 60 | 61 | var items []*structs.ACLPolicyListStub 62 | err := p.client.listResources(path.Join(aclPoliciesPath, "/"), nil, &items) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return items, nil 68 | } 69 | -------------------------------------------------------------------------------- /api/agent.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/seashell/drago/drago/structs" 7 | ) 8 | 9 | const ( 10 | agentPath = "/api/agent" 11 | ) 12 | 13 | // Agent is a handle to the agent API 14 | type Agent struct { 15 | client *Client 16 | } 17 | 18 | // Agent returns a handle on the agent endpoints. 19 | func (c *Client) Agent() *Agent { 20 | return &Agent{client: c} 21 | } 22 | 23 | // Self : 24 | func (t *Agent) Self() (*structs.Agent, error) { 25 | 26 | var agent *structs.Agent 27 | err := t.client.getResource(path.Join(agentPath, "self"), "", &agent) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return agent, nil 33 | } 34 | -------------------------------------------------------------------------------- /api/config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/url" 5 | "time" 6 | ) 7 | 8 | const ( 9 | // DefaultAddress is the default Drago server address. 10 | DefaultAddress = "http://127.0.0.1:8080" 11 | 12 | // DefaultTimeout is the default request timeout. 13 | DefaultTimeout = 2 * time.Second 14 | ) 15 | 16 | // Config contains configurations for Drago's API client. 17 | type Config struct { 18 | // URL of the Drago server (e.g. http://127.0.0.1:8080). 19 | Address string 20 | 21 | // Token to be used for authentication. 22 | Token string 23 | 24 | // Request timeout. 25 | Timeout time.Duration 26 | } 27 | 28 | // DefaultConfig returns a default configuration for Drago's API client. 29 | func DefaultConfig() *Config { 30 | config := &Config{ 31 | Address: DefaultAddress, 32 | } 33 | return config 34 | } 35 | 36 | // Validate validates the configurations contained wihin the Config struct. 37 | func (c *Config) Validate() error { 38 | if _, err := url.Parse(c.Address); err != nil { 39 | return err 40 | } 41 | return nil 42 | } 43 | 44 | // Merge merges two API client configurations. 45 | func (c *Config) Merge(b *Config) *Config { 46 | 47 | if b == nil { 48 | return c 49 | } 50 | 51 | result := *c 52 | 53 | if b.Address != "" { 54 | result.Address = b.Address 55 | } 56 | if b.Token != "" { 57 | result.Token = b.Token 58 | } 59 | if b.Timeout != 0 { 60 | result.Timeout = b.Timeout 61 | } 62 | 63 | return &result 64 | } 65 | -------------------------------------------------------------------------------- /api/error.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "fmt" 4 | 5 | type CodedError struct { 6 | Message string 7 | Code int 8 | } 9 | 10 | func (e CodedError) Error() string { 11 | return fmt.Sprintf("%d (%s)", e.Code, e.Message) 12 | } 13 | -------------------------------------------------------------------------------- /api/network.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/seashell/drago/drago/structs" 7 | ) 8 | 9 | const ( 10 | networksPath = "/api/networks" 11 | ) 12 | 13 | // Networks is a handle to the nodes API 14 | type Networks struct { 15 | client *Client 16 | } 17 | 18 | // Networks returns a handle on the networks endpoints. 19 | func (c *Client) Networks() *Networks { 20 | return &Networks{client: c} 21 | } 22 | 23 | // Create : 24 | func (n *Networks) Create(network *structs.Network) error { 25 | 26 | err := n.client.createResource(networksPath, network, nil) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | return err 32 | } 33 | 34 | // Delete : 35 | func (n *Networks) Delete(id string) error { 36 | 37 | err := n.client.deleteResource(id, networksPath, nil) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | return nil 43 | } 44 | 45 | // Get : 46 | func (n *Networks) Get(id string) (*structs.Network, error) { 47 | 48 | var network *structs.Network 49 | err := n.client.getResource(networksPath, id, &network) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return network, nil 55 | } 56 | 57 | // List : 58 | func (n *Networks) List() ([]*structs.NetworkListStub, error) { 59 | 60 | var items []*structs.NetworkListStub 61 | err := n.client.listResources(path.Join(networksPath, "/"), nil, &items) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return items, nil 67 | } 68 | -------------------------------------------------------------------------------- /api/node.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/seashell/drago/drago/structs" 7 | ) 8 | 9 | const ( 10 | nodesPath = "/api/nodes" 11 | ) 12 | 13 | // Nodes is a handle to the nodes API 14 | type Nodes struct { 15 | client *Client 16 | } 17 | 18 | // Nodes returns a handle on the nodes endpoints. 19 | func (c *Client) Nodes() *Nodes { 20 | return &Nodes{client: c} 21 | } 22 | 23 | // Get : 24 | func (t *Nodes) Get(id string) (*structs.Node, error) { 25 | 26 | var node *structs.Node 27 | err := t.client.getResource(nodesPath, id, &node) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return node, nil 33 | } 34 | 35 | // List : 36 | func (t *Nodes) List(filters map[string][]string) ([]*structs.NodeListStub, error) { 37 | 38 | var items []*structs.NodeListStub 39 | err := t.client.listResources(path.Join(nodesPath, "/"), filters, &items) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return items, nil 45 | } 46 | -------------------------------------------------------------------------------- /build/Dockerfile.builder: -------------------------------------------------------------------------------- 1 | FROM golang:1.16.2-stretch as drago-builder 2 | 3 | ARG HOST_UID=${HOST_UID} 4 | ARG HOST_USER=${HOST_USER} 5 | 6 | RUN curl -sS http://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ 7 | echo "deb http://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ 8 | curl -sL http://deb.nodesource.com/setup_15.x | bash - && \ 9 | apt-get install -y nodejs && \ 10 | apt-get update && \ 11 | apt-get remove cmdtest && \ 12 | apt-get install -y yarn 13 | 14 | RUN apt-get install -y gcc-arm-linux-gnueabihf libc6-dev-armhf-cross \ 15 | gcc-aarch64-linux-gnu libc6-dev-arm64-cross 16 | 17 | RUN if [ "${HOST_USER}" != "root" ]; then \ 18 | (adduser -q --gecos "" --home /home/${HOST_USER} --disabled-password -u ${HOST_UID} ${HOST_USER} \ 19 | && chown -R "${HOST_UID}:${HOST_UID}" /home/${HOST_USER}); \ 20 | fi 21 | 22 | USER ${HOST_USER} -------------------------------------------------------------------------------- /build/Dockerfile.linux_amd64: -------------------------------------------------------------------------------- 1 | FROM alpine:3.13 2 | 3 | WORKDIR /home 4 | 5 | RUN apk add -U wireguard-tools 6 | 7 | COPY ./bin/linux_amd64/drago ./drago 8 | 9 | ENTRYPOINT [ "./drago" ] -------------------------------------------------------------------------------- /client/nic/controller_test.go: -------------------------------------------------------------------------------- 1 | package nic 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | structs "github.com/seashell/drago/drago/structs" 8 | ) 9 | 10 | func TestCreateInterface(t *testing.T) { 11 | config := &Config{ 12 | InterfacesPrefix: "drago", 13 | WireguardPath: "./wireguard", 14 | } 15 | 16 | c, err := NewController(config) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | key, err := c.GenerateKey() 22 | if err != nil { 23 | t.Error() 24 | } 25 | 26 | err = c.CreateInterfaceWithKey(&structs.Interface{ 27 | Name: "1234567890abcd", 28 | Address: "192.168.2.1/24", 29 | Peers: []*structs.Peer{}, 30 | }, key) 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | } 35 | 36 | func TestListInterfaces(t *testing.T) { 37 | 38 | config := &Config{ 39 | InterfacesPrefix: "drago", 40 | WireguardPath: "./wireguard", 41 | } 42 | 43 | c, err := NewController(config) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | ifaces, err := c.Interfaces() 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | for _, i := range ifaces { 54 | fmt.Printf("%s (%s)", i.Name, i.Address) 55 | } 56 | } 57 | 58 | func TestDeleteAllInterfaces(t *testing.T) { 59 | return 60 | config := &Config{ 61 | InterfacesPrefix: "drago", 62 | WireguardPath: "./wireguard", 63 | } 64 | 65 | c, err := NewController(config) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | 70 | err = c.DeleteAllInterfaces() 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/nic/nic.go: -------------------------------------------------------------------------------- 1 | package nic 2 | 3 | import ( 4 | structs "github.com/seashell/drago/drago/structs" 5 | ) 6 | 7 | // NetworkInterfaceController provides network configuration capabilities. 8 | type NetworkInterfaceController interface { 9 | Interfaces() ([]*structs.Interface, error) 10 | CreateInterface(iface *structs.Interface) error 11 | UpdateInterface(iface *structs.Interface) error 12 | DeleteInterfaceByAlias(s string) error 13 | DeleteInterfaceByName(s string) error 14 | DeleteAllInterfaces() error 15 | } 16 | 17 | type PrivateKeyStore interface { 18 | KeyByID(id string) (*PrivateKey, error) 19 | UpsertKey(key *PrivateKey) error 20 | DeleteKey(id string) error 21 | } 22 | 23 | type PrivateKey struct { 24 | ID string 25 | Key string 26 | CreatedAt int64 27 | } 28 | -------------------------------------------------------------------------------- /client/state/inmem/inmem.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | 7 | "github.com/seashell/drago/drago/structs" 8 | ) 9 | 10 | type Repository struct { 11 | logger log.Logger 12 | 13 | // interface_id -> value 14 | interfaces map[string]*structs.Interface 15 | 16 | mu sync.RWMutex 17 | } 18 | 19 | func NewRepository(logger log.Logger) *Repository { 20 | return &Repository{ 21 | interfaces: make(map[string]*structs.Interface), 22 | logger: logger, 23 | } 24 | } 25 | 26 | func (r *Repository) Name() string { 27 | return "inmem" 28 | } 29 | 30 | func (r *Repository) Interfaces() ([]*structs.Interface, error) { 31 | r.mu.RLock() 32 | defer r.mu.RUnlock() 33 | 34 | ifaces := make([]*structs.Interface, 0, len(r.interfaces)) 35 | for _, v := range r.interfaces { 36 | ifaces = append(ifaces, v) 37 | } 38 | 39 | return ifaces, nil 40 | } 41 | 42 | func (r *Repository) UpsertInterface(iface *structs.Interface) error { 43 | r.mu.Lock() 44 | defer r.mu.Unlock() 45 | r.interfaces[iface.ID] = iface 46 | return nil 47 | } 48 | 49 | func (r *Repository) DeleteInterfaces(ids []string) error { 50 | r.mu.Lock() 51 | defer r.mu.Unlock() 52 | 53 | for _, id := range ids { 54 | delete(r.interfaces, id) 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /client/state/state.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import ( 4 | "github.com/seashell/drago/client/nic" 5 | "github.com/seashell/drago/drago/structs" 6 | ) 7 | 8 | // Repository : 9 | type Repository interface { 10 | Name() string 11 | 12 | // Client state 13 | Interfaces() ([]*structs.Interface, error) 14 | UpsertInterface(*structs.Interface) error 15 | DeleteInterfaces(id []string) error 16 | 17 | // Key store 18 | KeyByID(id string) (*nic.PrivateKey, error) 19 | UpsertKey(key *nic.PrivateKey) error 20 | DeleteKey(id string) error 21 | } 22 | -------------------------------------------------------------------------------- /client/util.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "reflect" 7 | 8 | "github.com/seashell/drago/drago/structs" 9 | ) 10 | 11 | type Diff struct { 12 | created []string 13 | deleted []string 14 | updated []string 15 | unchanged []string 16 | } 17 | 18 | // Read file contents if they exist, or persist and return a default value otherwise. 19 | func (c *Client) readFileLazy(path string, s string) (string, error) { 20 | 21 | var out string 22 | 23 | buf, err := ioutil.ReadFile(path) 24 | if err != nil && !os.IsNotExist(err) { 25 | return "", err 26 | } 27 | 28 | if len(buf) != 0 { 29 | out = string(buf) 30 | } else { 31 | out = s 32 | if err := ioutil.WriteFile(path, []byte(s), 0700); err != nil { 33 | return "", err 34 | } 35 | } 36 | 37 | return out, nil 38 | } 39 | 40 | func interfacesDiff(old, new map[string]*structs.Interface) Diff { 41 | 42 | diff := Diff{} 43 | 44 | for _, vold := range old { 45 | if _, ok := new[vold.ID]; !ok { 46 | diff.deleted = append(diff.deleted, vold.ID) 47 | } 48 | } 49 | 50 | for _, vnew := range new { 51 | if vold, ok := old[vnew.ID]; !ok { 52 | diff.created = append(diff.created, vnew.ID) 53 | } else { 54 | vnew = vold.Merge(vnew) 55 | if !reflect.DeepEqual(vold, vnew) { 56 | diff.updated = append(diff.updated, vnew.ID) 57 | } else { 58 | diff.unchanged = append(diff.unchanged, vold.ID) 59 | } 60 | } 61 | } 62 | 63 | return diff 64 | } 65 | -------------------------------------------------------------------------------- /command/acl.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | ) 9 | 10 | // ACLCommand : 11 | type ACLCommand struct { 12 | UI cli.UI 13 | } 14 | 15 | // Name : 16 | func (c *ACLCommand) Name() string { 17 | return "acl" 18 | } 19 | 20 | // Synopsis : 21 | func (c *ACLCommand) Synopsis() string { 22 | return "Interact with ACL policies and tokens" 23 | } 24 | 25 | // Run : 26 | func (c *ACLCommand) Run(ctx context.Context, args []string) int { 27 | return cli.CommandReturnCodeHelp 28 | } 29 | 30 | // Help : 31 | func (c *ACLCommand) Help() string { 32 | h := ` 33 | Usage: drago acl [options] [args] 34 | 35 | This command groups subcommands for interacting with ACL policies and tokens. 36 | It can be used to bootstrap Drago's ACL system, create policies that restrict 37 | access, and generate tokens from those policies. 38 | 39 | Bootstrap ACLs: 40 | 41 | $ drago acl bootstrap 42 | 43 | Please see the individual subcommand help for detailed usage information. 44 | ` 45 | return strings.TrimSpace(h) 46 | } 47 | -------------------------------------------------------------------------------- /command/acl_policy.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | ) 9 | 10 | // ACLPolicyCommand : 11 | type ACLPolicyCommand struct { 12 | UI cli.UI 13 | } 14 | 15 | // Name : 16 | func (c *ACLPolicyCommand) Name() string { 17 | return "acl policy" 18 | } 19 | 20 | // Synopsis : 21 | func (c *ACLPolicyCommand) Synopsis() string { 22 | return "Interact with ACL policies" 23 | } 24 | 25 | // Run : 26 | func (c *ACLPolicyCommand) Run(ctx context.Context, args []string) int { 27 | return cli.CommandReturnCodeHelp 28 | } 29 | 30 | // Help : 31 | func (c *ACLPolicyCommand) Help() string { 32 | h := ` 33 | Usage: drago acl policy [options] [args] 34 | 35 | This command groups subcommands for interacting with ACL policies. 36 | 37 | Please see the individual subcommand help for detailed usage information. 38 | ` 39 | return strings.TrimSpace(h) 40 | } 41 | -------------------------------------------------------------------------------- /command/acl_token.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | ) 9 | 10 | // ACLTokenCommand : 11 | type ACLTokenCommand struct { 12 | UI cli.UI 13 | } 14 | 15 | // Name : 16 | func (c *ACLTokenCommand) Name() string { 17 | return "acl token" 18 | } 19 | 20 | // Synopsis : 21 | func (c *ACLTokenCommand) Synopsis() string { 22 | return "Interact with ACL tokens" 23 | } 24 | 25 | // Run : 26 | func (c *ACLTokenCommand) Run(ctx context.Context, args []string) int { 27 | return cli.CommandReturnCodeHelp 28 | } 29 | 30 | // Help : 31 | func (c *ACLTokenCommand) Help() string { 32 | h := ` 33 | Usage: drago acl token [options] [args] 34 | 35 | This command groups subcommands for interacting with ACL tokens. 36 | 37 | Please see the individual subcommand help for detailed usage information. 38 | ` 39 | return strings.TrimSpace(h) 40 | } 41 | -------------------------------------------------------------------------------- /command/connection.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | ) 9 | 10 | // ConnectionCommand : 11 | type ConnectionCommand struct { 12 | UI cli.UI 13 | } 14 | 15 | // Name : 16 | func (c *ConnectionCommand) Name() string { 17 | return "connection" 18 | } 19 | 20 | // Synopsis : 21 | func (c *ConnectionCommand) Synopsis() string { 22 | return "Interact with connections" 23 | } 24 | 25 | // Run : 26 | func (c *ConnectionCommand) Run(ctx context.Context, args []string) int { 27 | return cli.CommandReturnCodeHelp 28 | } 29 | 30 | // Help : 31 | func (c *ConnectionCommand) Help() string { 32 | h := ` 33 | Usage: drago connection [options] [args] 34 | 35 | This command groups subcommands for interacting with connections. 36 | 37 | Please see the individual subcommand help for detailed usage information. 38 | 39 | General Options: 40 | ` + GlobalOptions() + ` 41 | ` 42 | return strings.TrimSpace(h) 43 | } 44 | -------------------------------------------------------------------------------- /command/interface.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | ) 9 | 10 | // InterfaceCommand : 11 | type InterfaceCommand struct { 12 | UI cli.UI 13 | } 14 | 15 | // Name : 16 | func (c *InterfaceCommand) Name() string { 17 | return "interface" 18 | } 19 | 20 | // Synopsis : 21 | func (c *InterfaceCommand) Synopsis() string { 22 | return "Interact with interfaces" 23 | } 24 | 25 | // Run : 26 | func (c *InterfaceCommand) Run(ctx context.Context, args []string) int { 27 | return cli.CommandReturnCodeHelp 28 | } 29 | 30 | // Help : 31 | func (c *InterfaceCommand) Help() string { 32 | h := ` 33 | Usage: drago interface [options] [args] 34 | 35 | This command groups subcommands for interacting with interfaces. 36 | 37 | Please see the individual subcommand help for detailed usage information. 38 | ` 39 | return strings.TrimSpace(h) 40 | } 41 | -------------------------------------------------------------------------------- /command/network.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | ) 9 | 10 | // NetworkCommand : 11 | type NetworkCommand struct { 12 | UI cli.UI 13 | } 14 | 15 | // Name : 16 | func (c *NetworkCommand) Name() string { 17 | return "network" 18 | } 19 | 20 | // Synopsis : 21 | func (c *NetworkCommand) Synopsis() string { 22 | return "Interact with networks" 23 | } 24 | 25 | // Run : 26 | func (c *NetworkCommand) Run(ctx context.Context, args []string) int { 27 | return cli.CommandReturnCodeHelp 28 | } 29 | 30 | // Help : 31 | func (c *NetworkCommand) Help() string { 32 | h := ` 33 | Usage: drago network [options] [args] 34 | 35 | This command groups subcommands for interacting with networks. 36 | 37 | Please see the individual subcommand help for detailed usage information. 38 | ` 39 | return strings.TrimSpace(h) 40 | } 41 | -------------------------------------------------------------------------------- /command/node.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | ) 9 | 10 | // NodeCommand : 11 | type NodeCommand struct { 12 | UI cli.UI 13 | } 14 | 15 | // Name : 16 | func (c *NodeCommand) Name() string { 17 | return "node" 18 | } 19 | 20 | // Synopsis : 21 | func (c *NodeCommand) Synopsis() string { 22 | return "Interact with nodes" 23 | } 24 | 25 | // Run : 26 | func (c *NodeCommand) Run(ctx context.Context, args []string) int { 27 | return cli.CommandReturnCodeHelp 28 | } 29 | 30 | // Help : 31 | func (c *NodeCommand) Help() string { 32 | h := ` 33 | Usage: drago node [options] [args] 34 | 35 | This command groups subcommands for interacting with nodes. 36 | 37 | Please see the individual subcommand help for detailed usage information. 38 | ` 39 | return strings.TrimSpace(h) 40 | } 41 | -------------------------------------------------------------------------------- /command/ui.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os/exec" 7 | "runtime" 8 | "strings" 9 | 10 | cli "github.com/seashell/drago/pkg/cli" 11 | ) 12 | 13 | // UICommand : 14 | type UICommand struct { 15 | UI cli.UI 16 | 17 | Command 18 | } 19 | 20 | // Name : 21 | func (c *UICommand) Name() string { 22 | return "acl" 23 | } 24 | 25 | // Synopsis : 26 | func (c *UICommand) Synopsis() string { 27 | return "Open the Drago web UI" 28 | } 29 | 30 | // Run : 31 | func (c *UICommand) Run(ctx context.Context, args []string) int { 32 | 33 | url := "http://127.0.0.1:8080" 34 | if c.address != "" { 35 | url = c.address 36 | } 37 | 38 | c.UI.Output(fmt.Sprintf(`Opening URL "%s"`, url)) 39 | 40 | if err := openBrowser(url); err != nil { 41 | c.UI.Error(err.Error()) 42 | } 43 | 44 | return 0 45 | } 46 | 47 | // Help : 48 | func (c *UICommand) Help() string { 49 | h := ` 50 | Usage: drago ui [options] [args] 51 | 52 | Open the Drago web UI in the default browser. 53 | 54 | General Options: 55 | ` + GlobalOptions() + ` 56 | ` 57 | return strings.TrimSpace(h) 58 | } 59 | 60 | // Credits: https://gist.github.com/hyg/9c4afcd91fe24316cbf0 61 | func openBrowser(url string) error { 62 | 63 | var err error 64 | 65 | switch runtime.GOOS { 66 | case "linux": 67 | err = exec.Command("xdg-open", url).Start() 68 | case "windows": 69 | err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() 70 | case "darwin": 71 | err = exec.Command("open", url).Start() 72 | default: 73 | err = fmt.Errorf("unsupported platform") 74 | } 75 | if err != nil { 76 | return err 77 | } 78 | 79 | return nil 80 | 81 | } 82 | -------------------------------------------------------------------------------- /command/util.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | api "github.com/seashell/drago/api" 8 | ) 9 | 10 | // Returns the node ID of the local agent, in case it is a client. 11 | // Otherwise, returns an error. 12 | func localAgentNodeID(api *api.Client) (string, error) { 13 | 14 | self, err := api.Agent().Self() 15 | if err != nil { 16 | return "", fmt.Errorf("could not retrieve agent info: %s", err) 17 | } 18 | 19 | clientStats, ok := self.Stats["client"] 20 | if !ok { 21 | return "", fmt.Errorf("not running in client mode") 22 | } 23 | 24 | nodeID, ok := clientStats["node_id"] 25 | if !ok { 26 | return "", fmt.Errorf("could not determine node id") 27 | } 28 | 29 | return nodeID, nil 30 | } 31 | 32 | func valueOrPlaceholder(s *string, p string) string { 33 | if s != nil { 34 | return *s 35 | } 36 | return p 37 | } 38 | 39 | // TODO: improve how we clean JSON strings 40 | func cleanJSONString(s string) string { 41 | 42 | s = strings.TrimSpace(s) 43 | s = strings.ReplaceAll(s, `""`, "N/A") 44 | s = strings.ReplaceAll(s, `{}`, "\n N/A") 45 | 46 | s = strings.ReplaceAll(s, " ", " ") 47 | s = strings.ReplaceAll(s, `,`, "") 48 | 49 | s = strings.ReplaceAll(s, "{", "") 50 | s = strings.ReplaceAll(s, "}", "") 51 | s = strings.ReplaceAll(s, `"`, "") 52 | 53 | s = strings.TrimLeftFunc(s, func(r rune) bool { 54 | return r == '\n' 55 | }) 56 | 57 | s = strings.TrimRightFunc(s, func(r rune) bool { 58 | return r == '\n' || r == ' ' 59 | }) 60 | 61 | return s 62 | 63 | } 64 | -------------------------------------------------------------------------------- /command/version.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | cli "github.com/seashell/drago/pkg/cli" 8 | version "github.com/seashell/drago/version" 9 | ) 10 | 11 | // VersionCommand : 12 | type VersionCommand struct { 13 | UI cli.UI 14 | } 15 | 16 | // Name : 17 | func (c *VersionCommand) Name() string { 18 | return "version" 19 | } 20 | 21 | // Synopsis : 22 | func (c *VersionCommand) Synopsis() string { 23 | return "Print the Drago version" 24 | } 25 | 26 | // Run : 27 | func (c *VersionCommand) Run(ctx context.Context, args []string) int { 28 | c.UI.Output(version.GetVersion().VersionNumber()) 29 | return 0 30 | } 31 | 32 | // Help : 33 | func (c *VersionCommand) Help() string { 34 | h := ` 35 | Usage: drago version [options] 36 | 37 | Version prints out the Drago version. 38 | 39 | General Options: 40 | ` + GlobalOptions() + ` 41 | ` 42 | return strings.TrimSpace(h) 43 | } 44 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Drago Documentation Website 2 | 3 | This subdirectory contains the source for the Drago website. 4 | 5 | ### Overview 6 | We use [Docsify](https://docsify.js.org/) for generating the website on the fly based on markdown files. 7 | 8 | ### Contributing 9 | In order to contribute to the Drago docs, simply fork the repo, edit the markdown in this directory, and submit a pull request. Also, feel to add any new assets such as diagrams and screenshots. -------------------------------------------------------------------------------- /docs/_404.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

404 - Page not found

5 |

For more information, please contact us.

6 |
7 |
8 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 |
2 | drago-logo 4 |
5 | 6 |

drago

7 | 8 |

9 | Go report: A+ 10 | GitHub 11 | Gitter 12 | 13 | GitHub stars 14 | 15 |

16 | 17 | > A flexible configuration manager for WireGuard networks. 18 | 19 | - Single-binary, lightweight 20 | - Encrypted node-to-node communication 21 | - Support for different WireGuard implementations 22 | - Slick management dashboard 23 | - Extensible via REST API 24 | 25 | [GitHub](https://github.com/seashell/drago/) 26 | [Get Started](/docs/) 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/docs/_navbar.md -------------------------------------------------------------------------------- /docs/api/acl-policies.md: -------------------------------------------------------------------------------- 1 | # ACL Policies HTTP API 2 | -------------------------------------------------------------------------------- /docs/api/acl-tokens.md: -------------------------------------------------------------------------------- 1 | # ACL Tokens HTTP API 2 | -------------------------------------------------------------------------------- /docs/api/connections.md: -------------------------------------------------------------------------------- 1 | # Connections HTTP API 2 | -------------------------------------------------------------------------------- /docs/api/interfaces.md: -------------------------------------------------------------------------------- 1 | # Interfaces HTTP API 2 | -------------------------------------------------------------------------------- /docs/api/networks.md: -------------------------------------------------------------------------------- 1 | # Networks HTTP API 2 | 3 | The `/networks/` endpoints are used to manage Networks. 4 | 5 | ## List Networks 6 | 7 | This endpoint lists all Networks. 8 | 9 | | **Method** | **Path** | **Produces** | 10 | |------------|-----------------|--------------------| 11 | | `GET` | `/networks/` | `application/json` | 12 | 13 | 14 | | **ACL Required** | 15 | |-------------------------------------| 16 | | `management` for all policies. | 17 | 18 | 19 | ### Parameters 20 | 21 | ### Sample Request 22 | 23 | ### Sample Response -------------------------------------------------------------------------------- /docs/api/nodes.md: -------------------------------------------------------------------------------- 1 | # Nodes HTTP API 2 | -------------------------------------------------------------------------------- /docs/api/status.md: -------------------------------------------------------------------------------- 1 | # Status HTTP API -------------------------------------------------------------------------------- /docs/api/ui.md: -------------------------------------------------------------------------------- 1 | # UI HTTP API -------------------------------------------------------------------------------- /docs/docs/README.md: -------------------------------------------------------------------------------- 1 | # Drago Documentation 2 | 3 | Welcome to the Drago documentation. This page is meant to be a reference for all available features and options in Drago. 4 | 5 | > [!NOTE] 6 | > Drago is in active development, and its documentation is still a work-in-progress. We, therefore, apologize beforehand if information in this page is unclear or outdated. If you have any questions about a specific topic, or suggestions related to the project, please drop us a message on our [Gitter channel](https://gitter.im/seashell/drago) or create a [GitHub issue](https://github.com/seashell/drago/issues). 7 | > 8 | -------------------------------------------------------------------------------- /docs/docs/commands/README.md: -------------------------------------------------------------------------------- 1 | # Drago Commands (CLI) 2 | 3 | 4 | Drago is controlled via a very easy to use command-line interface (CLI). Drago is only a single command-line application: `drago`, which takes subcommands such as `agent` or `status`. The complete list of subcommands is in the navigation to the left. 5 | 6 | The Drago CLI is a well-behaved command line application. In erroneous cases, a non-zero exit status will be returned. It also responds to `-h` and `--help` as you would most likely expect. 7 | 8 | To view a list of the available commands at any time, just run Drago with no arguments. To get help for any specific subcommand, run the subcommand with the `-h` argument. 9 | 10 | Each command has been conveniently documented on this website. Links to each command can be found on the left. 11 | 12 | ### Remote usage 13 | 14 | The Drago CLI can be used to interact with a remote Drago agent. 15 | 16 | To do so, set the `DRAGO_ADDR` environment variable or use the `--address=` flag when running commands. 17 | 18 | ``` 19 | $ DRAGO_ADDR=https://:8080 drago agent-info 20 | $ drago agent-info --address=https://remote-address:4646 21 | ``` 22 | 23 | The provided address must be reachable from your local machine. 24 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/README.md: -------------------------------------------------------------------------------- 1 | # Command: acl 2 | 3 | The `acl` command is used to interact with ACL policies and tokens. 4 | 5 | ## Usage 6 | 7 | Usage: `drago acl [options]` 8 | 9 | Run `drago acl [options] -h` for help on a specific subcommand. 10 | 11 | Available subcommands: 12 | 13 | - [`acl bootstrap`](/docs/commands/acl/bootstrap): Bootstrap the ACL system 14 | - [`acl policy apply`](/docs/commands/acl/policy-apply): Create or update an ACL policy 15 | - [`acl policy delete`](/docs/commands/acl/policy-delete): Delete an existing ACL policy 16 | - [`acl policy info`](/docs/commands/acl/policy-info): Display info on an existing ACL policy 17 | - [`acl policy list`](/docs/commands/acl/policy-list): List available ACL policies 18 | - [`acl token create`](/docs/commands/acl/token-create): Create a new ACL token 19 | - [`acl token delete`](/docs/commands/acl/token-delete): Delete an existing ACL token 20 | - [`acl token info`](/docs/commands/acl/token-info): Display info on an existing ACL token 21 | - [`acl token list`](/docs/commands/acl/token-list): List available ACL tokens 22 | - [`acl token self`](/docs/commands/acl/token-self): Display info on self ACL token 23 | - [`acl token update`](/docs/commands/acl/token-update): Update an existing ACL token 24 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/bootstrap.md: -------------------------------------------------------------------------------- 1 | # Command: acl bootstrap 2 | 3 | The `acl bootstrap` command can be used to bootstrap the initial ACL token. 4 | 5 | ## General Options 6 | 7 | - `--address=` 8 | The address of the Drago server. 9 | Overrides the `DRAGO_ADDR` environment variable if set. 10 | Defaults to `http://127.0.0.1:8080`. 11 | 12 | - `--token=` 13 | The token used to authenticate with the Drago server. 14 | Overrides the `DRAGO_TOKEN` environment variable if set. 15 | Defaults to `""`. 16 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/policy-apply.md: -------------------------------------------------------------------------------- 1 | # Command: acl policy apply 2 | 3 | The `acl policy apply` command is used to create or update ACL policies. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl policy apply [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Apply Options 24 | 25 | - `--description=`: Sets the description of the ACL policy. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/policy-delete.md: -------------------------------------------------------------------------------- 1 | # Command: acl policy delete 2 | 3 | The `acl policy delete` command is used to delete an existing ACL policy. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl policy delete [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/policy-info.md: -------------------------------------------------------------------------------- 1 | # Command: acl policy info 2 | 3 | The `acl policy info` command is used to display detailed information about an existing ACL policy. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl policy info [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## List Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/policy-list.md: -------------------------------------------------------------------------------- 1 | # Command: acl policy list 2 | 3 | The `acl policy list` command is used to list existing ACL policies. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl policy list [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## List Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/token-create.md: -------------------------------------------------------------------------------- 1 | # Command: acl token create 2 | 3 | The `acl token create` command is used to issue new ACL tokens. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl token create [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Create Options 24 | 25 | - `--name=`: Sets the name of the ACL token. 26 | 27 | - `--type=`: Sets the type of the ACL token. Must be either "client" or "management". If not provided, defaults to "client". 28 | 29 | - `--policy=`: Specifies policies to associate with a client token. Can be specified multiple times. 30 | 31 | - `--json`: Enable JSON output. 32 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/token-delete.md: -------------------------------------------------------------------------------- 1 | # Command: acl token delete 2 | 3 | The `acl token delete` command is used to delete an existing ACL token. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl token delete 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/token-info.md: -------------------------------------------------------------------------------- 1 | # Command: acl token info 2 | 3 | The `acl token info` command is used to display detailed information on an existing ACL token. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl token info [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/token-list.md: -------------------------------------------------------------------------------- 1 | # Command: acl token list 2 | 3 | The `acl token list` command is used to list all available ACL tokens. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl token list [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/token-self.md: -------------------------------------------------------------------------------- 1 | # Command: acl token self 2 | 3 | The `acl token self` command is used to display detailed information on the currently set ACL token. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl token self [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/acl/token-update.md: -------------------------------------------------------------------------------- 1 | # Command: acl token update 2 | 3 | The `acl token update` command is used to update an existing ACL token. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago acl token update [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Update Options 24 | 25 | - `--name=`: Sets the name of the ACL token. 26 | 27 | - `--type=`: Sets the type of the ACL token. Must be either "client" or "management". If not provided, defaults to "client". 28 | 29 | - `--policy=`: Specifies policies to associate with a client token. Can be specified multiple times. 30 | 31 | - `--json`: Enable JSON output. 32 | -------------------------------------------------------------------------------- /docs/docs/commands/agent-info.md: -------------------------------------------------------------------------------- 1 | # Command: agent-info 2 | 3 | The `agent-info` command is used to display configurations and status from the agent to which the CLI is connected. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago agent-info [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Agent Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/agent.md: -------------------------------------------------------------------------------- 1 | # Command: agent 2 | 3 | The `agent` command is likely Drago's most important command. It is used to start client and/or server agent. 4 | 5 | ## Command-line Options 6 | 7 | The `agent` command accepts the following arguments (note that some of them can be overriden by CLI flags): 8 | 9 | - `--client`: Enable client mode on the local agent. 10 | 11 | - `--config=`: Indicate the path to a configuration file containing configurations to be used by the Drago agent. Can be speficied multiple times. 12 | 13 | - `--data-dir=`: 14 | 15 | - `--dev`: Enable development mode on the local agent. This means the agent will execute both as a client and as a server, with sane configurations that are ideal for development. 16 | 17 | - `--node=`: 18 | 19 | - `--server`: Enable server mode on the local agent. 20 | 21 | - `--servers=`: 22 | 23 | - `--wireguard-path`: Path to a userspace WireGuard implementation. Both `wireguard-go` and `cloudflare/boringtun` are supported. If not provided, the WireGuard kernel module will be used. 24 | 25 | ## Example 26 | 27 | ``` 28 | $ drago agent --server 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/docs/commands/connection/create.md: -------------------------------------------------------------------------------- 1 | # Command: connection create 2 | 3 | The `connection create` command is used to create a connection. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago connection create [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | 27 | - `--allow-all`: Enables routing of all traffic in this connection. 28 | 29 | - `--keepalive=`: Time interval between persistent keepalive packets. Defaults to 0, which disables the feature. 30 | -------------------------------------------------------------------------------- /docs/docs/commands/connection/list.md: -------------------------------------------------------------------------------- 1 | # Command: connection list 2 | 3 | The `connection list` command is used to list all available connections. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago connection list [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/connection/update.md: -------------------------------------------------------------------------------- 1 | # Command: connection update 2 | 3 | The `connection update` command is used to update an existing connection. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago connection update [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | -------------------------------------------------------------------------------- /docs/docs/commands/interface/list.md: -------------------------------------------------------------------------------- 1 | # Command: interface list 2 | 3 | The `interface list` command is used to list all available interfaces. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago interface list [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | 27 | - `--self`: Filter results by the local node ID. Can not be used with the --node filter flag. 28 | 29 | - `--node=`: Filter results by node ID. Can not be used with the --self filter flag. 30 | 31 | - `--network=`: Filter results by network. 32 | -------------------------------------------------------------------------------- /docs/docs/commands/interface/update.md: -------------------------------------------------------------------------------- 1 | # Command: interface update 2 | 3 | The `interface update` command is used to update an existing interface. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago interface update [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Update Options 24 | 25 | - `--address`: Interface IP address in CIDR notation 26 | -------------------------------------------------------------------------------- /docs/docs/commands/network/create.md: -------------------------------------------------------------------------------- 1 | # Command: network create 2 | 3 | The `network create` command is used to create a new network. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago network create [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Create Options 24 | 25 | - `--range=`: Network IP address range in CIDR notation. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/network/delete.md: -------------------------------------------------------------------------------- 1 | # Command: network delete 2 | 3 | The `network delete` command is used to delete an existing network. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago network delete [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | -------------------------------------------------------------------------------- /docs/docs/commands/network/info.md: -------------------------------------------------------------------------------- 1 | # Command: network info 2 | 3 | The `network info` command is used to display detailed information about an existing network. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago network info [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/network/list.md: -------------------------------------------------------------------------------- 1 | # Command: network list 2 | 3 | The `network list` command is used to list all available networks. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago network list [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## List Options 24 | 25 | - `--json`: Enable JSON output. 26 | -------------------------------------------------------------------------------- /docs/docs/commands/node/info.md: -------------------------------------------------------------------------------- 1 | # Command: node info 2 | 3 | The `node info` command is used to display detailed information about an existing node. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago node info [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--self`: Query the status of the local node. 26 | 27 | - `--json`: Enable JSON output. 28 | -------------------------------------------------------------------------------- /docs/docs/commands/node/join.md: -------------------------------------------------------------------------------- 1 | # Command: node join 2 | 3 | The `node join` command is used to have the local client node join an existing network. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago node join [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | -------------------------------------------------------------------------------- /docs/docs/commands/node/leave.md: -------------------------------------------------------------------------------- 1 | # Command: node leave 2 | 3 | The `node leave` command is used to have the local client node leave a specific network. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago node leave [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | -------------------------------------------------------------------------------- /docs/docs/commands/node/list.md: -------------------------------------------------------------------------------- 1 | # Command: node list 2 | 3 | The `node list` command is used to list all available nodes. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago node list [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--json`: Enable JSON output. 26 | 27 | - `--meta=`: Filter nodes by metadata. 28 | 29 | - `--status=`: Filter nodes by status. 30 | -------------------------------------------------------------------------------- /docs/docs/commands/node/status.md: -------------------------------------------------------------------------------- 1 | # Command: node status 2 | 3 | The `node status` command is used to list the status of one or more registered client nodes. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago node status [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address=` 14 | The address of the Drago server. 15 | Overrides the `DRAGO_ADDR` environment variable if set. 16 | Defaults to `http://127.0.0.1:8080`. 17 | 18 | - `--token=` 19 | The token used to authenticate with the Drago server. 20 | Overrides the `DRAGO_TOKEN` environment variable if set. 21 | Defaults to `""`. 22 | 23 | ## Info Options 24 | 25 | - `--self`: Query the status of the local node. 26 | 27 | - `--json`: Enable JSON output. 28 | -------------------------------------------------------------------------------- /docs/docs/commands/ui.md: -------------------------------------------------------------------------------- 1 | # Command: ui 2 | 3 | The `ui` command is used to open Drago's Web UI. 4 | 5 | ## Usage 6 | 7 | ``` 8 | drago ui [options] 9 | ``` 10 | 11 | ## General Options 12 | 13 | - `--address`: 14 | - `--token`: 15 | -------------------------------------------------------------------------------- /docs/docs/configuration/README.md: -------------------------------------------------------------------------------- 1 | # Drago Configuration 2 | 3 | Drago agents can be configured via HCL configuration files or command-line flags. 4 | -------------------------------------------------------------------------------- /docs/docs/configuration/acl.md: -------------------------------------------------------------------------------- 1 | # `acl` Block 2 | 3 | The `acl`block is used to configure the Drago ACL system. 4 | 5 | ## `acl` Parameters 6 | 7 | - `enabled` `(bool: false)` - Defines whether if ACL is enabled or not. All other configurations in this section are only applied if `enabled` is set to `true`. 8 | -------------------------------------------------------------------------------- /docs/docs/configuration/client.md: -------------------------------------------------------------------------------- 1 | # `client` Block 2 | 3 | The `client`block is used to configure the Drago agent when operating in client mode. 4 | 5 | ## `client` Parameters 6 | 7 | - `enabled` `(bool: false)` - Specify if the agent will run in client mode. 8 | -------------------------------------------------------------------------------- /docs/docs/configuration/server.md: -------------------------------------------------------------------------------- 1 | # `server` Block 2 | 3 | The `server`block is used to configure the Drago agent when operating in server mode. 4 | 5 | ## `server` Parameters 6 | 7 | - `enabled` `(bool: false)` - Specify if the agent will run in server mode. 8 | -------------------------------------------------------------------------------- /docs/docs/installing/README.md: -------------------------------------------------------------------------------- 1 | # Installing Drago 2 | 3 | We are working to make Drago available as a pre-compiled binary or as a package for several operating systems. 4 | 5 | Meanwhile, you can build Drago from source. 6 | 7 | ## Compiling from Source 8 | 9 | To compile from source, you will need Go installed and configured properly. 10 | 11 | 1. Clone the Drago repository: 12 | ```bash 13 | $ git clone https://github.com/seashell/drago.git 14 | $ cd drago 15 | ``` 16 | 2. Download all necessary Go modules into the module cache: 17 | 18 | ```bash 19 | $ go mod download 20 | ``` 21 | 3. Build Drago for your current system. 22 | 23 | ```bash 24 | $ go build -o ./build/drago 25 | ``` 26 | 27 | ## Verify the binary 28 | 29 | To verify Drago was built correctly, run the binary: 30 | 31 | ```bash 32 | $ ./build/drago 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/docs/installing/quickstart.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | This page describes methods for installing Drago in different environments. 4 | 5 | > [!WARNING] 6 | > This section is still a work-in-progress. If you think you can contribute, please see our [contribution guidelines](docs/../../../contributing.md). 7 | -------------------------------------------------------------------------------- /docs/docs/internals/README.md: -------------------------------------------------------------------------------- 1 | # Drago Internals 2 | 3 | This section covers the internals of Drago and explains the technical details of how Drago functions, its architecture, and sub-systems. 4 | 5 | > [!NOTE] 6 | > Knowledge of Drago internals is not required to use Drago. If you aren't interested in the internals of Drago, you may safely skip this section. If you are operating Drago, we recommend understanding the internals. 7 | -------------------------------------------------------------------------------- /docs/docs/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | > [!WARNING] 4 | > This section is still a work-in-progress. If you think you can contribute, please see our [contribution guidelines](docs/../../../contributing.md). 5 | 6 | 7 | Drago is a flexible configuration manager for WireGuard networks that is designed to make it simple to configure network overlays spanning heterogeneous nodes distributed across different clouds and physical locations. 8 | 9 | Drago is meant to be simple and provide a solid foundation for higher-level functionality. Need automatic IP assignment, dynamic firewall rules, or some kind of telemetry? You are free to implement it on top of the already existing APIs. 10 | 11 | ## Use-cases 12 | 13 | - Secure home automation, SSH access, etc 14 | - Establish secure VPNs for your company 15 | - Manage access to sensitive services deployed to private hosts 16 | - Expose development servers for debugging and demonstration purposes 17 | - Establish multi-cloud clusters with ease 18 | - Build your own cloud with RaspberryPIs 19 | 20 | ## Main features 21 | 22 | - Single-binary, lightweight 23 | - Encrypted node-to-node communication 24 | - Support for different WireGuard implementations 25 | - Slick management dashboard 26 | - Extensible via REST API -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | > [!WARNING] 4 | > This section is still a work-in-progress. If you think you can contribute, please see our [contribution guidelines](docs/../../../contributing.md). 5 | 6 | ## Why Drago? 7 | 8 | WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform and widely deployable, being regarded as the most secure, easiest to use, and simplest VPN solution in the industry. 9 | 10 | WireGuard presents several advantages over other VPN solutions, but it does not allow for the dynamic configuration of network parameters such as IP addresses and firewall rules. Drago builds on top of WireGuard, allowing users to dynamically manage the configuration of their VPN networks, providing a unified control plane for overlays spanning from containers to virtual machines to IoT devices. -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/docs/favicon.ico -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Drago is released under the [Apache 2.0 license](https://github.com/seashell/drago/blob/master/LICENSE). 4 | 5 | -------------------------------------------------------------------------------- /drago/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/seashell/drago/pkg/acl" 7 | ) 8 | 9 | // AuthorizationHandler abstracts a handler capable of 10 | // authorizing subjects to perform specific operations on 11 | // resources at a given path 12 | type AuthorizationHandler interface { 13 | Authorize(ctx context.Context, sub, res, path, op string) error 14 | } 15 | 16 | // authorizationHandler implements the AuthorizationHandler 17 | // interface, thus being capable of authorizing operations. 18 | type authorizationHandler struct { 19 | resolver *acl.Resolver 20 | } 21 | 22 | // NewAuthorizationHandler returns a new AuthorizationHandler 23 | func NewAuthorizationHandler( 24 | model *acl.Model, 25 | secretResolver acl.SecretResolverFunc, 26 | policyResolver acl.PolicyResolverFunc) AuthorizationHandler { 27 | 28 | aclResolver, _ := acl.NewResolver(&acl.ResolverConfig{ 29 | Model: model, 30 | SecretResolver: secretResolver, 31 | PolicyResolver: policyResolver, 32 | }) 33 | 34 | return &authorizationHandler{ 35 | resolver: aclResolver, 36 | } 37 | } 38 | 39 | // Authorize checks whether or not the specified operation is authorized or 40 | // not on the targeted resource and path, potentially returning an error. 41 | func (h *authorizationHandler) Authorize(ctx context.Context, sub, res, path, op string) error { 42 | 43 | acl, err := h.resolver.ResolveSecret(ctx, sub) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return acl.CheckAuthorized(ctx, res, path, op) 49 | } 50 | -------------------------------------------------------------------------------- /drago/auth/policy.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "github.com/seashell/drago/pkg/acl" 4 | 5 | // Policy implements the acl.Policy interface. 6 | type Policy struct { 7 | name string 8 | rules []acl.Rule 9 | } 10 | 11 | // NewPolicy : 12 | func NewPolicy(name string, rules []acl.Rule) *Policy { 13 | return &Policy{name, rules} 14 | } 15 | 16 | // Name returns the name of the policy. 17 | func (p *Policy) Name() string { 18 | return p.name 19 | } 20 | 21 | // Rules return the policy rules. 22 | func (p *Policy) Rules() []acl.Rule { 23 | return p.rules 24 | } 25 | 26 | // AddRule appends an acl.Rule to the policy rules, returning the resulting slice. 27 | func (p *Policy) AddRule(r acl.Rule) []acl.Rule { 28 | p.rules = append(p.rules, r) 29 | return p.rules 30 | } 31 | -------------------------------------------------------------------------------- /drago/auth/rule.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | // Rule implements the acl.Rule interface. 4 | type Rule struct { 5 | resource string 6 | path string 7 | capabilities []string 8 | } 9 | 10 | // NewRule : 11 | func NewRule(res, path string, caps []string) *Rule { 12 | return &Rule{res, path, caps} 13 | } 14 | 15 | // Resource returns the type of resource targeted 16 | // by the rule. 17 | func (r *Rule) Resource() string { 18 | return r.resource 19 | } 20 | 21 | // Path returns the pattern this rule uses to 22 | // match targeted specific resource instances. 23 | func (r *Rule) Path() string { 24 | return r.path 25 | } 26 | 27 | // Capabilities return a slice of capabilities enabled 28 | // on the targeted resource instances. 29 | func (r *Rule) Capabilities() []string { 30 | return r.capabilities 31 | } 32 | -------------------------------------------------------------------------------- /drago/auth/token.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | // Token implements the acl.Token interface 4 | type Token struct { 5 | privileged bool 6 | policies []string 7 | } 8 | 9 | // NewToken : 10 | func NewToken(privileged bool, policies []string) *Token { 11 | return &Token{privileged, policies} 12 | } 13 | 14 | // Policies returns a slice of policies associated with 15 | // the token. 16 | func (t *Token) Policies() []string { 17 | return t.policies 18 | } 19 | 20 | // IsPrivileged returns true if the token has privileged access, 21 | // and false otherwise. 22 | func (t *Token) IsPrivileged() bool { 23 | return t.privileged 24 | } 25 | -------------------------------------------------------------------------------- /drago/state/etcd/acl_state.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/seashell/drago/drago/structs" 9 | ) 10 | 11 | // ACLState : 12 | func (r *StateRepository) ACLState(ctx context.Context) (*structs.ACLState, error) { 13 | 14 | key := aclStateKey() 15 | 16 | res, err := r.client.Get(ctx, key) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | if res.Count == 0 { 22 | return nil, errors.New("not found") 23 | } 24 | 25 | state := &structs.ACLState{} 26 | 27 | err = decodeValue(res.Kvs[0].Value, state) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return state, nil 33 | } 34 | 35 | // ACLSetState : 36 | func (r *StateRepository) ACLSetState(ctx context.Context, s *structs.ACLState) error { 37 | 38 | key := aclStateKey() 39 | 40 | _, err := r.client.Put(ctx, key, encodeValue(s)) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func aclStateKey() string { 49 | return fmt.Sprintf("%s/%s/global/%s", defaultPrefix, "acl", "state") 50 | } 51 | -------------------------------------------------------------------------------- /drago/state/inmem/acl_policy.go: -------------------------------------------------------------------------------- 1 | package inmem 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strings" 7 | 8 | structs "github.com/seashell/drago/drago/structs" 9 | ) 10 | 11 | const ( 12 | resourceTypePolicy = "policy" 13 | ) 14 | 15 | // ACLPolicies : 16 | func (r *StateRepository) ACLPolicies(ctx context.Context) ([]*structs.ACLPolicy, error) { 17 | prefix := resourcePrefix(resourceTypePolicy) 18 | 19 | items := []*structs.ACLPolicy{} 20 | for el := range r.kv.Iter() { 21 | if strings.HasPrefix(el.Key, prefix) { 22 | if t, ok := el.Value.(*structs.ACLPolicy); ok { 23 | items = append(items, t) 24 | } 25 | } 26 | } 27 | 28 | return items, nil 29 | } 30 | 31 | // ACLPolicyByName : 32 | func (r *StateRepository) ACLPolicyByName(ctx context.Context, name string) (*structs.ACLPolicy, error) { 33 | key := resourceKey(resourceTypePolicy, name) 34 | if v, found := r.kv.Get(key); found { 35 | return v.(*structs.ACLPolicy), nil 36 | } 37 | return nil, errors.New("not found") 38 | } 39 | 40 | // UpsertACLPolicy : 41 | func (r *StateRepository) UpsertACLPolicy(ctx context.Context, p *structs.ACLPolicy) error { 42 | key := resourceKey(resourceTypePolicy, p.Name) 43 | r.kv.Set(key, p) 44 | return nil 45 | } 46 | 47 | // DeleteACLPolicies : 48 | func (r *StateRepository) DeleteACLPolicies(ctx context.Context, names []string) error { 49 | for _, name := range names { 50 | key := resourceKey(resourceTypePolicy, name) 51 | r.kv.Delete((key)) 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /drago/state/inmem/acl_state.go: -------------------------------------------------------------------------------- 1 | package inmem 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | structs "github.com/seashell/drago/drago/structs" 9 | ) 10 | 11 | // ACLState : 12 | func (r *StateRepository) ACLState(ctx context.Context) (*structs.ACLState, error) { 13 | key := aclStateKey() 14 | if v, found := r.kv.Get(key); found { 15 | return v.(*structs.ACLState), nil 16 | } 17 | return nil, errors.New("not found") 18 | } 19 | 20 | // ACLSetState : 21 | func (r *StateRepository) ACLSetState(ctx context.Context, s *structs.ACLState) error { 22 | key := aclStateKey() 23 | r.kv.Set(key, s) 24 | return nil 25 | } 26 | 27 | func aclStateKey() string { 28 | return fmt.Sprintf("%s/%s/global/%s", defaultPrefix, "acl", "state") 29 | } 30 | -------------------------------------------------------------------------------- /drago/status.go: -------------------------------------------------------------------------------- 1 | package drago 2 | 3 | import ( 4 | auth "github.com/seashell/drago/drago/auth" 5 | state "github.com/seashell/drago/drago/state" 6 | structs "github.com/seashell/drago/drago/structs" 7 | ) 8 | 9 | // StatusService is used to check on server status 10 | type StatusService struct { 11 | config *Config 12 | state state.Repository 13 | authHandler auth.AuthorizationHandler 14 | } 15 | 16 | // NewStatusService ... 17 | func NewStatusService(config *Config, state state.Repository, authHandler auth.AuthorizationHandler) *StatusService { 18 | return &StatusService{ 19 | config: config, 20 | state: state, 21 | authHandler: authHandler, 22 | } 23 | } 24 | 25 | // Ping is used to check for connectivity 26 | func (s *StatusService) Ping(args structs.GenericRequest, out *structs.GenericResponse) error { 27 | return nil 28 | } 29 | 30 | // Version returns the version of the server 31 | func (s *StatusService) Version(in structs.GenericRequest, out *structs.StatusVersionResponse) error { 32 | out.Version = s.config.Version.VersionNumber() 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /drago/structs/acl.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | // ACLState ... 4 | type ACLState struct { 5 | RootTokenID string 6 | RootTokenResetIndex int 7 | } 8 | 9 | // Update ... 10 | func (s *ACLState) Update(rootID string) error { 11 | s.RootTokenID = rootID 12 | s.RootTokenResetIndex++ 13 | return nil 14 | } 15 | 16 | // ACLBootstrapRequest : 17 | type ACLBootstrapRequest struct { 18 | ResetIndex uint64 19 | 20 | WriteRequest 21 | } 22 | 23 | // ResolveACLTokenRequest : 24 | type ResolveACLTokenRequest struct { 25 | Secret string 26 | 27 | QueryOptions 28 | } 29 | 30 | // ResolveACLTokenResponse : 31 | type ResolveACLTokenResponse struct { 32 | ACLToken *ACLToken 33 | 34 | Response 35 | } 36 | -------------------------------------------------------------------------------- /drago/structs/agent.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | // Agent : 4 | type Agent struct { 5 | Config map[string]interface{} 6 | Stats map[string]map[string]string 7 | } 8 | -------------------------------------------------------------------------------- /drago/structs/config/acl.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/seashell/drago/pkg/acl" 7 | ) 8 | 9 | // ACLConfig : 10 | type ACLConfig struct { 11 | Enabled bool 12 | // TokenTTL controls for how long we keep ACL tokens in cache. 13 | TokenTTL time.Duration 14 | 15 | // Model contains the ACL model 16 | Model *acl.Model 17 | } 18 | 19 | // DefaultACLConfig : 20 | func DefaultACLConfig() *ACLConfig { 21 | return &ACLConfig{ 22 | Enabled: false, 23 | TokenTTL: 30 * time.Second, 24 | Model: acl.NewModel(), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /drago/structs/errors.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import ( 4 | "fmt" 5 | 6 | "errors" 7 | ) 8 | 9 | const ( 10 | errPermissionDenied = "Permission denied" 11 | errTokenNotFound = "ACL Token not found" 12 | errACLDisabled = "ACL disabled" 13 | errACLAlreadyBootstrapped = "ACL already bootstrapped" 14 | errInvalidInput = "Invalid input" 15 | errNotFound = "Resource not found" 16 | errInternal = "Internal error" 17 | ) 18 | 19 | var ( 20 | // ErrPermissionDenied : 21 | ErrPermissionDenied = errors.New(errPermissionDenied) 22 | 23 | // ErrACLAlreadyBootstrapped ... 24 | ErrACLAlreadyBootstrapped = errors.New(errACLAlreadyBootstrapped) 25 | 26 | // ErrACLDisabled ... 27 | ErrACLDisabled = errors.New(errACLDisabled) 28 | 29 | // ErrInternal ... 30 | ErrInternal = errors.New(errInternal) 31 | 32 | // ErrInvalidInput ... 33 | ErrInvalidInput = errors.New(errInvalidInput) 34 | 35 | // ErrNotFound ... 36 | ErrNotFound = errors.New(errNotFound) 37 | ) 38 | 39 | // Error : 40 | type Error struct { 41 | Message string 42 | } 43 | 44 | // NewError ... 45 | func NewError(base error, extra ...interface{}) error { 46 | msg := base.Error() 47 | for _, v := range extra { 48 | msg = fmt.Sprintf("%s : %v", msg, v) 49 | } 50 | return &Error{ 51 | Message: msg, 52 | } 53 | } 54 | 55 | func (e Error) Error() string { 56 | return e.Message 57 | } 58 | 59 | func NewInternalError(msg string) error { 60 | return NewError(ErrInternal, msg) 61 | } 62 | 63 | func NewInvalidInputError(msg string) error { 64 | return NewError(ErrInvalidInput, msg) 65 | } 66 | -------------------------------------------------------------------------------- /drago/structs/status.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | // StatusVersionResponse ... 4 | type StatusVersionResponse struct { 5 | Version string 6 | 7 | QueryOptions 8 | } 9 | -------------------------------------------------------------------------------- /drago/structs/structs.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/seashell/drago/pkg/validator" 8 | ) 9 | 10 | type Filters map[string][]string 11 | 12 | func (f Filters) Get(k string) []string { 13 | if v, ok := f[k]; ok { 14 | return v 15 | } 16 | return []string{} 17 | } 18 | 19 | func (f Filters) Add(k, v string) { 20 | f[k] = append(f[k], []string{v}...) 21 | } 22 | 23 | // QueryOptions contains information that is common to all read requests. 24 | type QueryOptions struct { 25 | AuthToken string 26 | Filters Filters 27 | } 28 | 29 | // WriteRequest contains information that is common to all write requests. 30 | type WriteRequest struct { 31 | AuthToken string 32 | } 33 | 34 | // Response contains information that is common to all responses. 35 | type Response struct { 36 | } 37 | 38 | // GenericRequest is used to request where no 39 | // specific information is needed. 40 | type GenericRequest struct { 41 | QueryOptions 42 | } 43 | 44 | // GenericResponse is used to respond to a request where no 45 | // specific response information is needed. 46 | type GenericResponse struct { 47 | Response 48 | } 49 | 50 | // Validate validates a struct/DTO, returning an error in case its 51 | // attributes are not compliant with the allowed values. 52 | func Validate(s interface{}) error { 53 | 54 | ctx := context.Background() 55 | v, err := validator.New(ctx) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | err = v.Validate(s) 61 | if err != nil { 62 | return errors.Wrap(errors.New("invalid struct"), err.Error()) 63 | } 64 | 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /drago/test/interface_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | -------------------------------------------------------------------------------- /drago/test/node_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "testing" 4 | 5 | func TestNodeRegister(t *testing.T) { 6 | 7 | } 8 | 9 | func TestNodeStateUpdate(t *testing.T) { 10 | 11 | } 12 | 13 | func TestNodeInterfacesUpdate(t *testing.T) { 14 | 15 | } 16 | func TestNodeJoinNetwork(t *testing.T) { 17 | 18 | } 19 | 20 | func TestNodeLeaveNetwork(t *testing.T) { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /example/client1.hcl: -------------------------------------------------------------------------------- 1 | data_dir = "/tmp/drago" 2 | bind_addr = "0.0.0.0" 3 | 4 | name = "node-1" 5 | 6 | advertise { 7 | peer = "192.168.100.13" 8 | } 9 | 10 | server { 11 | enabled = false 12 | } 13 | 14 | client { 15 | enabled = true 16 | servers = ["192.168.99.1:8081"] 17 | wireguard_path = "./boringtun" 18 | } 19 | -------------------------------------------------------------------------------- /example/client2.hcl: -------------------------------------------------------------------------------- 1 | data_dir = "/tmp/drago" 2 | bind_addr = "0.0.0.0" 3 | 4 | name = "node-2" 5 | 6 | advertise { 7 | peer = "192.168.100.14" 8 | } 9 | 10 | server { 11 | enabled = false 12 | } 13 | 14 | client { 15 | enabled = true 16 | servers = ["192.168.100.12:8081"] 17 | wireguard_path = "./wireguard" 18 | meta = { 19 | test_meta = "test_meta_value" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/server.hcl: -------------------------------------------------------------------------------- 1 | data_dir = "/tmp/drago" 2 | bind_addr = "0.0.0.0" 3 | ui = true 4 | 5 | server { 6 | enabled = true 7 | } 8 | 9 | client { 10 | enabled = false 11 | } 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/seashell/drago 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/caarlos0/env v3.5.0+incompatible 7 | github.com/dimiro1/banner v1.1.0 8 | github.com/fatih/color v1.10.0 9 | github.com/go-playground/validator/v10 v10.4.1 10 | github.com/hashicorp/go-cleanhttp v0.5.2 11 | github.com/hashicorp/hcl/v2 v2.9.1 12 | github.com/imdario/mergo v0.3.12 13 | github.com/joho/godotenv v1.3.0 14 | github.com/pkg/errors v0.9.1 15 | github.com/rodaine/table v1.0.1 16 | github.com/sirupsen/logrus v1.8.1 17 | github.com/spf13/pflag v1.0.5 18 | github.com/vishvananda/netlink v1.1.1-0.20200604160102-dc0e1b988c57 19 | github.com/vmihailenco/msgpack v4.0.4+incompatible 20 | go.etcd.io/bbolt v1.3.5 21 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 22 | go.uber.org/zap v1.16.0 23 | golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b 24 | ) 25 | -------------------------------------------------------------------------------- /init/README.md: -------------------------------------------------------------------------------- 1 | # init 2 | 3 | The `init` folder contains system init (systemd, upstart, sysv) and process manager/supervisor (runit, supervisord) configs for multiple platforms. 4 | 5 | ## Conventions 6 | 7 | On unix systems agent configurations shall be kept under `/etc/drago` and data under `/var/lib/drago/`. These directories are assumed to exist, and the Drago binary is assumed to be located at `/usr/bin/drago`. 8 | 9 | ## Agent configuration 10 | 11 | The following configuration files are provided as examples: 12 | 13 | * `demo/server.yml` 14 | * `demo/client.yml` 15 | 16 | Place one of these under `/etc/drago.d` depending on the host's role. You should use `server.yml` to configure a host as a server or `client.yml` to configure a host as a client. 17 | 18 | ## systemd 19 | 20 | On systems using `systemd`, the basic unit file under `systemd/drago.service` starts and stops the drago agent. Place it under `/etc/systemd/system/drago.service`. 21 | 22 | You can control Drago with `systemctl start|stop|restart drago`. -------------------------------------------------------------------------------- /init/systemd/drago.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Drago 3 | Wants=network-online.target 4 | After=network-online.target 5 | 6 | [Service] 7 | ExecReload=/bin/kill -HUP $MAINPID 8 | ExecStart=/usr/local/bin/drago agent --config /etc/drago.d 9 | KillMode=process 10 | KillSignal=SIGINT 11 | LimitNOFILE=65536 12 | LimitNPROC=infinity 13 | Restart=on-failure 14 | RestartSec=2 15 | StartLimitBurst=3 16 | StartLimitIntervalSec=10 17 | TasksMax=infinity 18 | OOMScoreAdjust=-1000 19 | 20 | [Install] 21 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /pkg/acl/config.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "github.com/seashell/drago/pkg/log" 5 | ) 6 | 7 | // ResolverConfig contains configurations for an ACL Resolver. 8 | type ResolverConfig struct { 9 | Logger log.Logger 10 | Model *Model 11 | SecretResolver SecretResolverFunc 12 | PolicyResolver PolicyResolverFunc 13 | } 14 | 15 | // DefaultResolverConfig returns default configurations 16 | // for an ACL Resolver. 17 | func DefaultResolverConfig() *ResolverConfig { 18 | return &ResolverConfig{ 19 | Logger: &simpleLogger{ 20 | fields: map[string]interface{}{}, 21 | options: log.LoggerOptions{ 22 | Prefix: "acl", 23 | Level: levelInfo, 24 | }, 25 | }, 26 | } 27 | } 28 | 29 | // Merge merges two ResolverConfig structs, returning the result. 30 | func (c *ResolverConfig) Merge(in *ResolverConfig) *ResolverConfig { 31 | res := *c 32 | if in.Logger != nil { 33 | res.Logger = in.Logger 34 | } 35 | if in.Model != nil { 36 | res.Model = in.Model 37 | } 38 | if in.SecretResolver != nil { 39 | res.SecretResolver = in.SecretResolver 40 | } 41 | if in.PolicyResolver != nil { 42 | res.PolicyResolver = in.PolicyResolver 43 | } 44 | return &res 45 | } 46 | -------------------------------------------------------------------------------- /pkg/acl/policy.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | // Policy represents a named set of rules for allowing 4 | // operations on specific resources. 5 | type Policy interface { 6 | Name() string 7 | Rules() []Rule 8 | } 9 | 10 | // Rule is used to allow operations on specific resources. In 11 | // addition to the name of the target resource type and the allowed 12 | // operations, a rule also specifies an optional glob pattern for 13 | // targeting specific instances of the resource. 14 | type Rule interface { 15 | // Resource targeted by this rule. 16 | Resource() string 17 | // Path used to target specific instances 18 | // of the target resource, if applicable. 19 | Path() string 20 | // Capabilities contains the actions allowed on 21 | // instances of a resource matching this rule. 22 | Capabilities() []string 23 | } 24 | -------------------------------------------------------------------------------- /pkg/acl/token.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | // Token ... 4 | type Token interface { 5 | IsPrivileged() bool 6 | Policies() []string 7 | } 8 | -------------------------------------------------------------------------------- /pkg/cli/command.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import "context" 4 | 5 | const ( 6 | CommandReturnCodeHelp = -18511 7 | ) 8 | 9 | // A Command is a runnable sub-command of a CLI 10 | type Command interface { 11 | // Help should return long-form help text that includes the command-line 12 | // usage, a brief few sentences explaining the function of the command, 13 | // and the complete list of flags the command accepts. 14 | Help() string 15 | 16 | // Synopsis should return a one-line, short synopsis of the command. 17 | // This should be less than 50 characters ideally. 18 | Synopsis() string 19 | 20 | // Run should run the actual command with the given CLI instance and 21 | // command-line arguments. It should return the exit status when it is 22 | // finished. 23 | Run(ctx context.Context, args []string) int 24 | } 25 | 26 | // A NamedCommand is a runnable sub-command of a CLI with a name 27 | type NamedCommand interface { 28 | Command 29 | Name() string 30 | } 31 | -------------------------------------------------------------------------------- /pkg/cli/command_mock.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import "context" 4 | 5 | // MockCommand is an implementation of Command that can be used for testing. 6 | // It is also used for automatically populating missing parent commands. 7 | type MockCommand struct { 8 | HelpText string 9 | SynopsisText string 10 | RunReturnCode int 11 | // Set by the command 12 | RunCalled bool 13 | RunArgs []string 14 | } 15 | 16 | func (c *MockCommand) Help() string { 17 | return c.HelpText 18 | } 19 | 20 | func (c *MockCommand) Run(ctx context.Context, args []string) int { 21 | c.RunCalled = true 22 | c.RunArgs = args 23 | return c.RunReturnCode 24 | } 25 | 26 | func (c *MockCommand) Synopsis() string { 27 | return c.SynopsisText 28 | } 29 | -------------------------------------------------------------------------------- /pkg/concurrent/map.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Map type that can be safely shared between 8 | // goroutines that require read/write access to a map 9 | type Map struct { 10 | sync.RWMutex 11 | items map[string]interface{} 12 | } 13 | 14 | func NewMap() *Map { 15 | return &Map{ 16 | items: map[string]interface{}{}, 17 | } 18 | } 19 | 20 | // Concurrent map item 21 | type MapItem struct { 22 | Key string 23 | Value interface{} 24 | } 25 | 26 | // Sets a key in a concurrent map 27 | func (cm *Map) Set(key string, value interface{}) { 28 | cm.Lock() 29 | defer cm.Unlock() 30 | cm.items[key] = value 31 | 32 | } 33 | 34 | // Gets a key from a concurrent map 35 | func (cm *Map) Get(key string) (interface{}, bool) { 36 | cm.RLock() 37 | defer cm.RUnlock() 38 | value, ok := cm.items[key] 39 | return value, ok 40 | } 41 | 42 | // Returns the length of a concurrent map 43 | func (cm *Map) Len() int { 44 | cm.RLock() 45 | defer cm.RUnlock() 46 | return len(cm.items) 47 | } 48 | 49 | // Deletes a key from a concurrent map 50 | func (cm *Map) Delete(key string) { 51 | cm.Lock() 52 | defer cm.Unlock() 53 | delete(cm.items, key) 54 | } 55 | 56 | // Iterates over the items in a concurrent map 57 | // Each item is sent over a channel, so that we can iterate 58 | // over the map using the builtin range keyword 59 | func (cm *Map) Iter() <-chan MapItem { 60 | c := make(chan MapItem) 61 | f := func() { 62 | snapshot := map[string]MapItem{} 63 | cm.RLock() 64 | for k, v := range cm.items { 65 | snapshot[k] = MapItem{Key: k, Value: v} 66 | } 67 | cm.RUnlock() 68 | for _, item := range snapshot { 69 | c <- item 70 | } 71 | close(c) 72 | } 73 | go f() 74 | 75 | return c 76 | } 77 | -------------------------------------------------------------------------------- /pkg/http/config.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | log "github.com/seashell/drago/pkg/log" 5 | ) 6 | 7 | // Config contains configurations for the http module 8 | type Config struct { 9 | // Server bind address in the form host:port 10 | BindAddress string 11 | 12 | // Handlers contains a mapping of paths to http.HandlerFunc 13 | Handlers map[string]Handler 14 | 15 | // Handlers contains middleware functions to be applied to handlers 16 | Middleware []Middleware 17 | 18 | // Logger 19 | Logger log.Logger 20 | } 21 | 22 | // DefaultConfig : 23 | func DefaultConfig() *Config { 24 | return &Config{ 25 | BindAddress: "0.0.0.0:9876", 26 | Handlers: map[string]Handler{}, 27 | Middleware: []Middleware{}, 28 | } 29 | } 30 | 31 | // Merge : 32 | func (s *Config) Merge(b *Config) *Config { 33 | result := *s 34 | if b.BindAddress != "" { 35 | result.BindAddress = b.BindAddress 36 | } 37 | if b.Logger != nil { 38 | result.Logger = b.Logger 39 | } 40 | if b.Handlers != nil { 41 | result.Handlers = b.Handlers 42 | } 43 | if b.Middleware != nil { 44 | result.Middleware = b.Middleware 45 | } 46 | return &result 47 | } 48 | -------------------------------------------------------------------------------- /pkg/http/handler.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // Handler : 8 | type Handler interface { 9 | Handle(http.ResponseWriter, *http.Request) (interface{}, error) 10 | } 11 | 12 | // HandlerFunc is a custom HTTP handler function that returns a struct 13 | // and an error that will be encoded and returned to the client 14 | type HandlerFunc func(http.ResponseWriter, *http.Request) (interface{}, error) 15 | -------------------------------------------------------------------------------- /pkg/http/middleware.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "net/http" 4 | 5 | // Middleware : 6 | type Middleware func(http.HandlerFunc) http.HandlerFunc 7 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | // Fields : Log fields 4 | type Fields map[string]interface{} 5 | 6 | // Options contains logging options which are 7 | // common to any logger 8 | type LoggerOptions struct { 9 | Prefix string 10 | Level string 11 | } 12 | 13 | // Logger : Logger interface 14 | type Logger interface { 15 | Debugf(format string, args ...interface{}) 16 | Infof(format string, args ...interface{}) 17 | Warnf(format string, args ...interface{}) 18 | Errorf(format string, args ...interface{}) 19 | Fatalf(format string, args ...interface{}) 20 | Panicf(format string, args ...interface{}) 21 | WithFields(fields Fields) Logger 22 | WithName(name string) Logger 23 | } 24 | -------------------------------------------------------------------------------- /pkg/radix/README.md: -------------------------------------------------------------------------------- 1 | # radix 2 | 3 | Simple implementation of radix trees in Go. 4 | 5 | A radix tree is a space-optimized/compressed version of a standard [trie](https://en.wikipedia.org/wiki/Trie), with every node that is a single child being merged with their parent. Unlike regular tries, the edges of a radix tree can hold strings, not only single characters. 6 | 7 | ### References 8 | [Wikipedia article about Radix trees](https://en.wikipedia.org/wiki/Radix_tree) 9 | [Compressing radix trees without too many tears](https://medium.com/basecs/compressing-radix-trees-without-too-many-tears-a2e658adb9a0) 10 | 11 | ### Related projects 12 | [armon/go-radix](https://github.com/armon/go-radix) 13 | [asergeyev/nradix/](https://github.com/asergeyev/nradix/) 14 | [zmap/go-iptree](https://github.com/zmap/go-iptree) -------------------------------------------------------------------------------- /pkg/radix/node.go: -------------------------------------------------------------------------------- 1 | package radix 2 | 3 | import "sort" 4 | 5 | type node struct { 6 | leaf *leaf 7 | edges []*edge 8 | } 9 | 10 | type leaf struct { 11 | key string 12 | value interface{} 13 | } 14 | 15 | func (n *node) isLeaf() bool { 16 | return n.leaf != nil 17 | } 18 | 19 | func (n *node) addEdge(e *edge) { 20 | n.edges = append(n.edges, e) 21 | n.sortEdges() 22 | } 23 | 24 | func (n *node) deleteEdge(s string) { 25 | // Apply binary search, since our edges sorted 26 | idx := sort.Search(len(n.edges), func(i int) bool { 27 | return n.edges[i].label >= s 28 | }) 29 | n.edges = append(n.edges[:idx], n.edges[idx+1:]...) 30 | } 31 | 32 | func (n *node) sortEdges() { 33 | sort.Slice(n.edges, func(i, j int) bool { 34 | return n.edges[i].label < n.edges[j].label 35 | }) 36 | } 37 | 38 | // getEdgeWithLongestCommonPrefix takes a query string 's' and finds 39 | // amongst all edges in the node the longest prefix they have in 40 | // common with 's', returning both the prefix and the edge. 41 | // 42 | // Example: 43 | // 44 | // query = "abcdef", edges = [{"aaa"},{"abcde"},{"de"},{"bde"}] 45 | // 46 | // output = "ab", {"abcde"} 47 | func (n *node) getEdgeWithLongestCommonPrefix(s string) (string, *edge) { 48 | for _, e := range n.edges { 49 | if p := longestCommonPrefix(s, e.label); p != "" { 50 | return p, e 51 | } 52 | } 53 | return "", nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/rpc/config.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "time" 5 | 6 | log "github.com/seashell/drago/pkg/log" 7 | ) 8 | 9 | type ServerConfig struct { 10 | //BindAddress 11 | BindAddress string 12 | 13 | // Logger 14 | Logger log.Logger 15 | 16 | // Receivers 17 | Receivers map[string]interface{} 18 | } 19 | 20 | func DefaultConfig() *ServerConfig { 21 | return &ServerConfig{ 22 | BindAddress: "0.0.0.0:8081", 23 | Receivers: map[string]interface{}{}, 24 | } 25 | } 26 | 27 | func (s *ServerConfig) Merge(b *ServerConfig) *ServerConfig { 28 | result := *s 29 | if b.BindAddress != "" { 30 | result.BindAddress = b.BindAddress 31 | } 32 | if b.Logger != nil { 33 | result.Logger = b.Logger 34 | } 35 | if b.Receivers != nil { 36 | result.Receivers = b.Receivers 37 | } 38 | return &result 39 | } 40 | 41 | type ClientConfig struct { 42 | // Logger 43 | Logger log.Logger 44 | 45 | // URL of the Drago server (e.g. http://127.0.0.1:8081). 46 | Address string 47 | 48 | // Timeout when dialing. 49 | DialTimeout time.Duration 50 | } 51 | 52 | func DefaultClientConfig() *ClientConfig { 53 | return &ClientConfig{ 54 | Address: "0.0.0.0:8081", 55 | DialTimeout: 2 * time.Second, 56 | } 57 | } 58 | 59 | func (c *ClientConfig) Merge(b *ClientConfig) *ClientConfig { 60 | result := *c 61 | if b.Address != "" { 62 | result.Address = b.Address 63 | } 64 | if b.Logger != nil { 65 | result.Logger = b.Logger 66 | } 67 | if b.DialTimeout != 0 { 68 | result.DialTimeout = b.DialTimeout 69 | } 70 | return &result 71 | } 72 | -------------------------------------------------------------------------------- /pkg/string/string.go: -------------------------------------------------------------------------------- 1 | package string 2 | 3 | import "strings" 4 | 5 | // SliceToString : Takes a slice containing strings and returns a comma-separated string 6 | func SliceToString(s []string) string { 7 | return strings.Join(s, ",") 8 | } 9 | 10 | // StringToSlice : Takes a comma-separated string and returns a slice 11 | func StringToSlice(s string) []string { 12 | return strings.Fields(strings.Replace(s, ",", " ", -1)) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func StrToPtr(s string) *string { 4 | return &s 5 | } 6 | 7 | func BoolToPtr(b bool) *bool { 8 | return &b 9 | } 10 | 11 | func IntToPtr(i int) *int { 12 | return &i 13 | } 14 | 15 | func Uint16ToPtr(i uint16) *uint16 { 16 | return &i 17 | } 18 | -------------------------------------------------------------------------------- /pkg/uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | ) 7 | 8 | func Generate() string { 9 | buf := make([]byte, 16) 10 | if _, err := rand.Read(buf); err != nil { 11 | panic(fmt.Errorf("failed to read random bytes: %v", err)) 12 | } 13 | return fmt.Sprintf("%x-%x-%x-%x-%x", 14 | buf[0:4], 15 | buf[4:6], 16 | buf[6:8], 17 | buf[8:10], 18 | buf[10:16]) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | 7 | "github.com/go-playground/validator/v10" 8 | ) 9 | 10 | func dashedAlphanumValidator(fl validator.FieldLevel) bool { 11 | re := regexp.MustCompile("^[a-z0-9][a-z0-9_-]*$") 12 | 13 | return re.MatchString(fl.Field().String()) 14 | } 15 | 16 | type Validator struct { 17 | v *validator.Validate 18 | } 19 | 20 | func New(ctx context.Context) (*Validator, error) { 21 | v := validator.New() 22 | v.RegisterValidation("dashed-alphanumeric", dashedAlphanumValidator) 23 | 24 | return &Validator{ 25 | v: v, 26 | }, nil 27 | } 28 | 29 | func (v Validator) Validate(i interface{}) error { 30 | return v.v.Struct(i) 31 | } 32 | -------------------------------------------------------------------------------- /plugin/admission/admission.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/rpc" 6 | ) 7 | 8 | // AdmissionPlugin : 9 | type AdmissionPlugin struct { 10 | logger log.Logger 11 | rpcServer *rpc.Server 12 | } 13 | 14 | // Config : 15 | type Config struct { 16 | } 17 | 18 | // NewAdmissionPlugin : Creates a new admission plugin object parameterized according to the provided configurations. 19 | func NewAdmissionPlugin(config *Config) (*AdmissionPlugin, error) { 20 | p := &AdmissionPlugin{} 21 | return p, nil 22 | } 23 | 24 | func main() { 25 | _, err := NewAdmissionPlugin(&Config{}) 26 | if err != nil { 27 | panic(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/lease/lease.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/rpc" 6 | ) 7 | 8 | // LeasePlugin : 9 | type LeasePlugin struct { 10 | logger log.Logger 11 | rpcServer *rpc.Server 12 | } 13 | 14 | // Config : 15 | type Config struct { 16 | } 17 | 18 | // NewLeasePlugin : Creates a new lease plugin object parameterized according to the provided configurations. 19 | func NewLeasePlugin(config *Config) (*LeasePlugin, error) { 20 | p := &LeasePlugin{} 21 | return p, nil 22 | } 23 | 24 | func main() { 25 | _, err := NewLeasePlugin(&Config{}) 26 | if err != nil { 27 | panic(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/mesh/mesh.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/rpc" 5 | 6 | log "github.com/seashell/drago/pkg/log" 7 | ) 8 | 9 | // TODO: Implementation of the drago.Plugin interface, with services exposed through RPC 10 | // Whenever a new interface is created (event InterfaceCreated), this plugin interacts with the 11 | // Drago RPC API in order to Link this interface with every other publicly exposed interface in 12 | // the same network, thus creating a mesh overlay. 13 | 14 | // MeshPlugin : 15 | type MeshPlugin struct { 16 | logger log.Logger 17 | rpcServer *rpc.Server 18 | } 19 | 20 | // Config : 21 | type Config struct { 22 | } 23 | 24 | // NewMeshPlugin : Creates a new mesh plugin object parameterized according to the provided configurations. 25 | func NewMeshPlugin(config *Config) (*MeshPlugin, error) { 26 | p := &MeshPlugin{} 27 | return p, nil 28 | } 29 | 30 | func main() { 31 | _, err := NewMeshPlugin(&Config{}) 32 | if err != nil { 33 | panic(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /plugin/notification/notification.go: -------------------------------------------------------------------------------- 1 | package notification 2 | 3 | import ( 4 | "net/rpc" 5 | 6 | log "github.com/seashell/drago/pkg/log" 7 | ) 8 | 9 | // TODO: Implementation of the drago.Plugin interface, with services exposed through RPC 10 | // Whenever an event occurs in the Drago server (e.g. a new node is registered, or becomes online), 11 | // this plugin interacts with the Drago RPC API in order to obtain information about this event 12 | // and notify users via email, Telegram, slack, etc. 13 | 14 | // NotificationPlugin : 15 | type NotificationPlugin struct { 16 | logger log.Logger 17 | rpcServer *rpc.Server 18 | } 19 | 20 | // Config : 21 | type Config struct { 22 | } 23 | 24 | // NewNotificationPlugin : Creates a new mesh plugin object parameterized according to the provided configurations. 25 | func NewNotificationPlugin(config *Config) (*NotificationPlugin, error) { 26 | p := &NotificationPlugin{} 27 | return p, nil 28 | } 29 | 30 | func main() { 31 | _, err := NewNotificationPlugin(&Config{}) 32 | if err != nil { 33 | panic(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | type Plugin interface{} 4 | -------------------------------------------------------------------------------- /ui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react" 4 | ] 5 | } -------------------------------------------------------------------------------- /ui/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["react-app","plugin:react/recommended", "airbnb", "prettier", "prettier/react"], 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "jsx": true 10 | }, 11 | "ecmaVersion": 12, 12 | "sourceType": "module" 13 | }, 14 | "plugins": ["react"], 15 | "rules": { 16 | "no-unused-vars": "warn", 17 | "react/jsx-curly-brace-presence": "off", 18 | "no-param-reassign": "off", 19 | "no-use-before-define": "off", 20 | "no-nested-ternary": "off", 21 | "react/jsx-filename-extension": "off", 22 | "import/no-extraneous-dependencies": "off", 23 | "jsx-a11y/anchor-is-valid": "off", 24 | "react-hooks/exhaustive-deps": "off", 25 | "react/jsx-props-no-spreading": "off", 26 | "react/destructuring-assignment": "off", 27 | "react/state-in-constructor": "off", 28 | "react/static-property-placement":"off" 29 | }, 30 | "settings": { 31 | "import/resolver": { 32 | "webpack": { 33 | "config": "config/webpack/eslint.js" 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "parser": "babel", 4 | "printWidth": 100, 5 | "arrowParens": "always", 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false 10 | } -------------------------------------------------------------------------------- /ui/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": ["./ui/"], 3 | "eslint.alwaysShowStatus": true, 4 | "eslint.packageManager": "yarn", 5 | "eslint.run": "onType", 6 | 7 | "prettier.prettierPath": "./node_modules/prettier", 8 | "prettier.configPath": "./.prettierrc", 9 | "prettier.packageManager": "yarn", 10 | 11 | "[javascript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "editor.formatOnSave": true, 14 | "editor.codeActionsOnSave": { 15 | "source.organizeImports": true, 16 | "source.fixAll.eslint": true, 17 | "source.fixAll.stylelint": true 18 | } 19 | }, 20 | 21 | } 22 | -------------------------------------------------------------------------------- /ui/build/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/build/.keep -------------------------------------------------------------------------------- /ui/config/rewired/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const { override, useBabelRc, useEslintRc, addWebpackResolve } = require('customize-cra') 4 | 5 | const rewireStyledComponents = require('react-app-rewire-styled-components') 6 | const resolve = require('../webpack/resolve') 7 | 8 | module.exports = { 9 | webpack: override( 10 | addWebpackResolve(resolve), 11 | // eslint-disable-next-line react-hooks/rules-of-hooks 12 | // useBabelRc(path.resolve(__dirname, '..', '..', '.babelrc')), 13 | // eslint-disable-next-line react-hooks/rules-of-hooks 14 | // useEslintRc(path.resolve(__dirname, '..', '..', '.eslintrc')), 15 | (config, env) => rewireStyledComponents(config, env, {}) 16 | ), 17 | } 18 | -------------------------------------------------------------------------------- /ui/config/webpack/eslint.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const resolve = require('./resolve') 4 | 5 | module.exports = { 6 | entry: ['../src/index'], 7 | output: { 8 | path: path.join(__dirname, 'dist'), 9 | filename: 'bundle.js', 10 | publicPath: '/static/', 11 | }, 12 | plugins: [], 13 | resolve, 14 | } 15 | -------------------------------------------------------------------------------- /ui/config/webpack/resolve.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | alias: { 5 | _assets: path.resolve(__dirname, '..', '..', 'src/assets/'), 6 | _components: path.resolve(__dirname, '..', '..', 'src/components/'), 7 | _containers: path.resolve(__dirname, '..', '..', 'src/containers/'), 8 | _modals: path.resolve(__dirname, '..', '..', 'src/modals/'), 9 | _graphql: path.resolve(__dirname, '..', '..', 'src/graphql/'), 10 | _utils: path.resolve(__dirname, '..', '..', 'src/utils/'), 11 | _views: path.resolve(__dirname, '..', '..', 'src/views/'), 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /ui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "baseUrl": "src", 5 | "paths": { 6 | "_components/*": ["components/*"], 7 | "_containers/*": ["containers/*"], 8 | "_modals/*": ["modals/*"], 9 | "_views/*": ["views/*"], 10 | "_graphql/*": ["graphql/*"], 11 | "_utils/*": ["utils/*"], 12 | "_assets/*": ["assets/*"] 13 | } 14 | }, 15 | "exclude": ["node_modules", "dist", "dist-server"] 16 | } 17 | -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | Drago 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "drago", 3 | "name": "drago", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#fafbfc" 15 | } 16 | -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Black.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Black.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-BlackItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-BlackItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Bold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Bold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-BoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-BoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Hairline.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Hairline.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Hairline.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Hairline.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-HairlineItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-HairlineItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-HairlineItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-HairlineItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Italic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Italic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Light.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Light.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-LightItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-LightItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Regular.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Lato-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Lato-Regular.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Black.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Black.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-BlackItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-BlackItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Bold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Bold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-BoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-BoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraBold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraBold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraLight.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraLight.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Italic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Italic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Light.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Light.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-LightItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-LightItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Medium.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Medium.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-MediumItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-MediumItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Regular.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Regular.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-SemiBold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-SemiBold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Thin.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-Thin.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ThinItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Montserrat-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Montserrat-ThinItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Black.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Black.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-BlackItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-BlackItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Bold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Bold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-BoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-BoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraBold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraBold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraLight.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraLight.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraLightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Italic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Italic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Light.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Light.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-LightItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-LightItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Medium.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Medium.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-MediumItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-MediumItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Regular.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Regular.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-SemiBold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-SemiBold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-SemiBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Thin.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-Thin.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ThinItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Raleway-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Raleway-ThinItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Black.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Black.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-BlackItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-BlackItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Bold.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-BoldItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-BoldItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Italic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Italic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Light.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Light.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-LightItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-LightItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Medium.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-MediumItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-MediumItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Regular.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Thin.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-ThinItalic.woff -------------------------------------------------------------------------------- /ui/src/assets/fonts/Roboto-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/assets/fonts/Roboto-ThinItalic.woff2 -------------------------------------------------------------------------------- /ui/src/assets/icons/down.svg: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /ui/src/assets/icons/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ui/src/assets/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/src/assets/icons/leave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ui/src/assets/icons/left.svg: -------------------------------------------------------------------------------- 1 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /ui/src/assets/icons/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/assets/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ui/src/assets/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ui/src/assets/icons/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ui/src/assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ui/src/assets/icons/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ui/src/assets/icons/times.svg: -------------------------------------------------------------------------------- 1 | 6 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /ui/src/assets/icons/unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ui/src/assets/icons/up.svg: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /ui/src/assets/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /ui/src/assets/illustrations/index.js: -------------------------------------------------------------------------------- 1 | import { ReactComponent as EmptyIllustration } from './empty.svg' 2 | import { ReactComponent as ErrorIllustration } from './error.svg' 3 | import { ReactComponent as NotFoundIllustration } from './not-found.svg' 4 | import { ReactComponent as UnauthorizedIllustration } from './unauthorized.svg' 5 | 6 | export default { 7 | NotFound: NotFoundIllustration, 8 | Error: ErrorIllustration, 9 | Empty: EmptyIllustration, 10 | Unauthorized: UnauthorizedIllustration, 11 | } 12 | -------------------------------------------------------------------------------- /ui/src/assets/index.js: -------------------------------------------------------------------------------- 1 | import icons from './icons' 2 | import illustrations from './illustrations' 3 | 4 | export { icons, illustrations } 5 | -------------------------------------------------------------------------------- /ui/src/components/avatar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { layout, space } from 'styled-system' 4 | 5 | import ReactAvatar, { ConfigProvider } from 'react-avatar' 6 | 7 | const StyledReactAvatar = styled(ReactAvatar)` 8 | ${layout} 9 | ${space} 10 | ` 11 | 12 | const Avatar = props => ( 13 | 14 | 15 | 16 | ) 17 | 18 | export default Avatar 19 | -------------------------------------------------------------------------------- /ui/src/components/back-link/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import { icons } from '_assets/' 5 | import Box from '_components/box' 6 | import Icon from '_components/icon' 7 | import Link from '_components/link' 8 | import Text from '_components/text' 9 | 10 | const Container = styled(Box)` 11 | width: max-content; 12 | opacity: 0.3; 13 | :hover { 14 | opacity: 1; 15 | } 16 | ` 17 | 18 | const BackLink = ({ text, to, ...props }) => ( 19 | 20 | 21 | 22 | } color="foreground2" size="14px" /> 23 | 24 | {text} 25 | 26 | 27 | 28 | 29 | ) 30 | 31 | BackLink.propTypes = { 32 | text: PropTypes.string.isRequired, 33 | to: PropTypes.string.isRequired, 34 | } 35 | 36 | export default BackLink 37 | -------------------------------------------------------------------------------- /ui/src/components/box/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { border, borderStyle, color, flexbox, grid, layout, shadow, space } from 'styled-system' 3 | import { containers } from '../../styles' 4 | 5 | const Box = styled.div` 6 | ${grid} 7 | ${space} 8 | ${color} 9 | ${layout} 10 | ${flexbox} 11 | ${border} 12 | ${containers} 13 | ${borderStyle} 14 | ${shadow} 15 | ` 16 | 17 | Box.defaultProps = { 18 | border: 'none', 19 | height: 'auto', 20 | display: 'flex', 21 | } 22 | 23 | export default Box 24 | -------------------------------------------------------------------------------- /ui/src/components/button/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | import { buttonStyle, shadow, layout, typography, border, space, color } from 'styled-system' 4 | 5 | const Button = styled.button` 6 | font-family: 'Lato'; 7 | font-weight: bold; 8 | letter-spacing: 0.04em; 9 | border: none; 10 | 11 | :disabled { 12 | opacity: 0.4; 13 | background: #ddd; 14 | border-color: #ddd; 15 | color: #666; 16 | box-shadow: none; 17 | cursor: not-allowed; 18 | } 19 | 20 | &:hover { 21 | filter: brightness(95%); 22 | transition: all 0.7s ease; 23 | } 24 | 25 | border-radius: 1px; 26 | 27 | ${buttonStyle} 28 | ${typography} 29 | ${shadow} 30 | ${layout} 31 | ${space} 32 | ${border} 33 | ${color} 34 | ` 35 | 36 | Button.defaultProps = { 37 | variant: 'primary', 38 | width: 'max-content', 39 | px: '24px', 40 | height: '50px', 41 | type: 'button', 42 | } 43 | 44 | export default Button 45 | -------------------------------------------------------------------------------- /ui/src/components/collapse/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import { space } from 'styled-system' 5 | import { icons } from '_assets' 6 | 7 | const Header = styled.div.attrs({ 8 | role: 'button', 9 | })` 10 | cursor: pointer; 11 | display: flex; 12 | align-items: center; 13 | justify-content: space-between; 14 | ${space} 15 | ` 16 | 17 | Header.defaultProps = { 18 | paddingTop: 2, 19 | paddingBottom: 2, 20 | } 21 | 22 | const Content = styled.div` 23 | ${space} 24 | ` 25 | 26 | const Collapse = ({ title, children, ...props }) => { 27 | const [isCollapseOpen, setCollapseOpen] = useState(props.isOpen) 28 | 29 | const handleHeaderClick = () => { 30 | setCollapseOpen(!isCollapseOpen) 31 | } 32 | 33 | return ( 34 | <> 35 |
36 | {title} 37 | {isCollapseOpen ? : } 38 |
39 | {isCollapseOpen && {children}} 40 | 41 | ) 42 | } 43 | 44 | Collapse.propTypes = { 45 | header: PropTypes.node, 46 | title: PropTypes.node.isRequired, 47 | isOpen: PropTypes.bool, 48 | children: PropTypes.node, 49 | } 50 | 51 | Collapse.defaultProps = { 52 | header: null, 53 | children: undefined, 54 | isOpen: false, 55 | } 56 | 57 | export default Collapse 58 | -------------------------------------------------------------------------------- /ui/src/components/empty-state/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import { illustrations } from '_assets/' 5 | import Box from '_components/box' 6 | import Text from '_components/text' 7 | 8 | const EmptyStateContainer = styled(Box).attrs({ 9 | border: 'thin', 10 | })` 11 | > svg { 12 | height: 160px; 13 | width: auto; 14 | } 15 | padding: 48px; 16 | flex-direction: column; 17 | align-items: center; 18 | justify-content: center; 19 | background-color: ${(props) => props.theme.colors.white}; 20 | border-color: ${(props) => props.theme.colors.neutralLighter}; 21 | ` 22 | 23 | const EmptyState = ({ image, title, description, extra, ...props }) => ( 24 | 25 | {image} 26 | 27 | {title} 28 | 29 | 30 | {description} 31 | 32 | {extra} 33 | 34 | ) 35 | 36 | EmptyState.propTypes = { 37 | image: PropTypes.node, 38 | title: PropTypes.string, 39 | description: PropTypes.string, 40 | extra: PropTypes.node, 41 | } 42 | 43 | EmptyState.defaultProps = { 44 | image: , 45 | title: 'No results found', 46 | description: `Oops! It seems that this query has not returned any resources.`, 47 | extra: null, 48 | } 49 | 50 | export default EmptyState 51 | -------------------------------------------------------------------------------- /ui/src/components/error-state/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import { illustrations } from '_assets/' 5 | import Box from '_components/box' 6 | import Text from '_components/text' 7 | 8 | const ErrorStateContainer = styled(Box)` 9 | svg { 10 | height: 160px; 11 | width: auto; 12 | } 13 | padding: 20px; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | background-color: ${(props) => props.theme.colors.background1}; 18 | ` 19 | 20 | const ErrorState = ({ title, description, extra }) => ( 21 | 22 | 23 | 24 | {title} 25 | 26 | 27 | {description} 28 | 29 | {extra} 30 | 31 | ) 32 | 33 | ErrorState.propTypes = { 34 | title: PropTypes.string, 35 | description: PropTypes.string, 36 | extra: PropTypes.node, 37 | } 38 | 39 | ErrorState.defaultProps = { 40 | title: 'Something is not right', 41 | description: 'It seems that an error has occurred. Check your network and try again.', 42 | extra: null, 43 | } 44 | 45 | export default ErrorState 46 | -------------------------------------------------------------------------------- /ui/src/components/flex/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { flex, layout, grid } from 'styled-system' 3 | 4 | const Flex = styled.div` 5 | display: flex; 6 | ${grid} 7 | ${flex} 8 | ${layout} 9 | ` 10 | 11 | export default Flex 12 | -------------------------------------------------------------------------------- /ui/src/components/headers/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import Avatar from '_components/avatar' 5 | import Text from '_components/text' 6 | import Box from '_components/box' 7 | 8 | const StyledBox = styled(Box).attrs({ 9 | height: '120px', 10 | display: 'flex', 11 | alignItems: 'center', 12 | padding: 0, 13 | mb: 3, 14 | })` 15 | > :first-child { 16 | margin-right: 16px; 17 | } 18 | ` 19 | 20 | const ProjectHeader = props => ( 21 | 22 | 23 |
24 | some-project 25 | This projects does something really well 26 |
27 |
28 | ) 29 | 30 | const NodeHeader = props => ( 31 | 32 | xyZ761kgVK 33 | This projects does something really well 34 | 35 | ) 36 | 37 | export { ProjectHeader, NodeHeader } 38 | -------------------------------------------------------------------------------- /ui/src/components/icon/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled, { css } from 'styled-components' 4 | import { color, border } from 'styled-system' 5 | 6 | import Box from '_components/box' 7 | 8 | export const Container = styled(Box)` 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | 13 | border: none; 14 | box-shadow: none; 15 | background: none; 16 | box-sizing: border-box; 17 | outline: none; 18 | 19 | flex-shrink: 0; 20 | width: ${(props) => props.size}; 21 | height: ${(props) => props.size}; 22 | 23 | svg { 24 | z-index: 1; 25 | width: 100%; 26 | height: auto; 27 | ${(props) => 28 | props.color && 29 | css` 30 | path { 31 | fill: ${props.theme.colors[props.color]}; 32 | } 33 | `} 34 | } 35 | ${border} 36 | ${color} 37 | ` 38 | 39 | const Icon = ({ icon, ...props }) => {icon} 40 | 41 | Icon.propTypes = { 42 | icon: PropTypes.node, 43 | size: PropTypes.string, 44 | color: PropTypes.string, 45 | } 46 | 47 | Icon.defaultProps = { 48 | icon: undefined, 49 | color: undefined, 50 | size: '24px', 51 | } 52 | 53 | export default Icon 54 | -------------------------------------------------------------------------------- /ui/src/components/inputs/number-input/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { layout, space } from 'styled-system' 3 | 4 | const NumberInput = styled.input.attrs({ 5 | type: 'number', 6 | })` 7 | box-sizing: border-box; 8 | 9 | border: none; 10 | border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; 11 | 12 | padding-bottom: 4px; 13 | 14 | font-family: Lato; 15 | font-size: 16px; 16 | 17 | width: 100%; 18 | 19 | :focus { 20 | border-bottom: 1px solid ${(props) => props.theme.colors.primary}; 21 | } 22 | :disabled { 23 | background: inherit; 24 | opacity: 0.5; 25 | border: none; 26 | } 27 | :invalid { 28 | border-bottom: 1px solid ${(props) => props.theme.colors.danger}; 29 | } 30 | ::placeholder { 31 | color: ${(props) => props.theme.colors.neutralLight}; 32 | } 33 | 34 | ::-webkit-outer-spin-button, 35 | ::-webkit-inner-spin-button { 36 | -webkit-appearance: none; 37 | margin: 0; 38 | } 39 | 40 | input[type='number'] { 41 | -moz-appearance: textfield; /* Firefox */ 42 | } 43 | 44 | ${space} 45 | ${layout} 46 | ` 47 | 48 | export default NumberInput 49 | -------------------------------------------------------------------------------- /ui/src/components/inputs/text-input/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { border, layout, space } from 'styled-system' 3 | 4 | const TextInput = styled.input.attrs({ 5 | type: 'text', 6 | autocomplete: 'off', 7 | })` 8 | box-sizing: border-box; 9 | 10 | border: none; 11 | border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; 12 | 13 | padding-bottom: 4px; 14 | 15 | background-color: ${(props) => props.theme.colors.white}; 16 | color: ${(props) => props.theme.colors.neutralDarker}; 17 | 18 | font-family: Lato; 19 | font-size: 16px; 20 | 21 | width: 100%; 22 | ::placeholder { 23 | color: ${(props) => props.theme.colors.neutralLight}; 24 | } 25 | :focus { 26 | border-bottom: 1px solid ${(props) => props.theme.colors.primary}; 27 | } 28 | :disabled { 29 | opacity: 0.5; 30 | cursor: not-allowed; 31 | border: none; 32 | } 33 | :invalid { 34 | border-bottom: 1px solid ${(props) => props.theme.colors.danger}; 35 | } 36 | 37 | ${border} 38 | ${space} 39 | ${layout} 40 | ` 41 | 42 | export default TextInput 43 | -------------------------------------------------------------------------------- /ui/src/components/link/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import styled from 'styled-components' 3 | import { color, typography, border, space, layout } from 'styled-system' 4 | 5 | import { Link } from '@reach/router' 6 | 7 | const StyledLink = styled(Link)` 8 | display: inline; 9 | 10 | font-family: Lato !important; 11 | 12 | :link { 13 | text-decoration: none; 14 | cursor: pointer; 15 | } 16 | 17 | :visited { 18 | text-decoration: inherit; 19 | cursor: auto; 20 | } 21 | 22 | color: inherit; 23 | 24 | :hover { 25 | ${(props) => props.hoverStyle} 26 | } 27 | 28 | &[aria-current] { 29 | ${(props) => props.activeStyle} 30 | } 31 | 32 | ${color} 33 | ${space} 34 | ${layout} 35 | ${border} 36 | ${typography} 37 | ` 38 | 39 | StyledLink.propTypes = { 40 | href: PropTypes.string, 41 | to: PropTypes.string.isRequired, 42 | } 43 | 44 | export default StyledLink 45 | -------------------------------------------------------------------------------- /ui/src/components/list/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { layout, space, flexbox } from 'styled-system' 3 | 4 | const List = styled.ul` 5 | overflow: hidden; 6 | overflow-y: auto; 7 | 8 | ::-webkit-scrollbar { 9 | background: transparent; 10 | width: 4px; 11 | height: 4px; 12 | } 13 | 14 | ::-webkit-scrollbar-button { 15 | display: none; 16 | } 17 | 18 | ::-webkit-scrollbar-track { 19 | background-color: transparent; 20 | } 21 | 22 | ::-webkit-scrollbar-track-piece { 23 | background-color: transparent; 24 | } 25 | 26 | ::-webkit-scrollbar-thumb { 27 | background-color: #ececf0; 28 | border-radius: 2px; 29 | } 30 | 31 | ::-webkit-scrollbar-corner { 32 | display: none; 33 | } 34 | 35 | ::-webkit-resizer { 36 | display: none; 37 | } 38 | 39 | ${flexbox} 40 | ${layout} 41 | ${space} 42 | ` 43 | 44 | export default List 45 | -------------------------------------------------------------------------------- /ui/src/components/nav/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | import Box from '_components/box' 4 | import Link from '_components/link' 5 | 6 | export const Nav = styled(Box).attrs({ 7 | width: '100%', 8 | borderBottom: 'medium', 9 | borderColor: 'neutralLighter', 10 | })`` 11 | 12 | export const HorizontalNavLink = styled(Link).attrs(({ theme }) => ({ 13 | py: 3, 14 | px: 2, 15 | color: 'neutral', 16 | fontSize: '14px', 17 | fontWeight: '500', 18 | marginBottom: '-2px', 19 | activeStyle: { 20 | color: theme.colors.primaryDark, 21 | 'border-bottom': `2px solid ${theme.colors.primary}`, 22 | }, 23 | hoverStyle: { 'border-bottom': `2px solid ${theme.colors.neutralDark}` }, 24 | }))` 25 | :first-child { 26 | margin-left: 0; 27 | } 28 | ` 29 | 30 | export const VerticalNavLink = styled(Link).attrs((props) => ({ 31 | py: 2, 32 | color: 'neutral', 33 | activeStyle: { color: props.theme.colors.primary }, 34 | }))`` 35 | -------------------------------------------------------------------------------- /ui/src/components/separator/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import styled, { css } from 'styled-components' 3 | import { color, layout, space } from 'styled-system' 4 | 5 | const Separator = styled.span` 6 | ${(props) => 7 | props.vertical 8 | ? css` 9 | width: 1px; 10 | height: 100%; 11 | ` 12 | : css` 13 | height: 1px; 14 | width: 100%; 15 | `}; 16 | display: block; 17 | ${layout} 18 | ${space} 19 | ${color} 20 | ` 21 | 22 | Separator.propTypes = { 23 | vertical: PropTypes.bool, 24 | } 25 | 26 | Separator.defaultProps = { 27 | vertical: false, 28 | bg: 'neutralLighter', 29 | } 30 | 31 | export default Separator 32 | -------------------------------------------------------------------------------- /ui/src/components/spinner/index.js: -------------------------------------------------------------------------------- 1 | import Jellyfish from './jellyfish' 2 | import Dragon from './dragon' 3 | 4 | export { Dragon, Jellyfish } 5 | -------------------------------------------------------------------------------- /ui/src/components/steps/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { space, layout, grid, flex } from 'styled-system' 3 | 4 | export const StepsContainer = styled.div` 5 | display: flex; 6 | counter-reset: step-counter; 7 | 8 | > * { 9 | opacity: 0.3; 10 | :nth-child(-n+${props => props.currentStep}){ 11 | opacity: 1; 12 | } 13 | } 14 | 15 | ${space} 16 | ${layout} 17 | ${grid} 18 | ${flex} 19 | ` 20 | 21 | export const Step = styled.p` 22 | color: ${props => props.theme.colors.primary}; 23 | display: flex; 24 | align-items: center; 25 | :before { 26 | content: counter(step-counter); 27 | display: flex; 28 | align-items: center; 29 | justify-content: center; 30 | margin-right: 8px; 31 | border-radius: 50%; 32 | counter-increment: step-counter; 33 | height: 28px; 34 | width: 28px; 35 | border: 1px solid ${props => props.theme.colors.primary}; 36 | } 37 | z-index: -1; 38 | ` 39 | -------------------------------------------------------------------------------- /ui/src/components/text/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { color, fontSize, fontWeight, layout, space, textAlign, textStyle } from 'styled-system' 3 | 4 | const Text = styled.div` 5 | strong { 6 | font-weight: bold; 7 | } 8 | ${textStyle} 9 | ${textAlign} 10 | ${space} 11 | ${layout} 12 | ${fontSize} 13 | ${fontWeight} 14 | ${color} 15 | ` 16 | 17 | export default Text 18 | -------------------------------------------------------------------------------- /ui/src/components/tooltip/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seashell/drago/9bc3763434c73a7f95a73ec69ad4bae2ed10ff08/ui/src/components/tooltip/index.js -------------------------------------------------------------------------------- /ui/src/components/unauthorized-state/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | import Box from '_components/box' 6 | import Text from '_components/text' 7 | 8 | import { illustrations } from '_assets/' 9 | 10 | const UnaurhorizedStateContainer = styled(Box).attrs({ 11 | border: 'none', 12 | height: '300px', 13 | })` 14 | svg { 15 | height: 300px; 16 | width: auto; 17 | } 18 | padding: 20px; 19 | flex-direction: column; 20 | align-items: center; 21 | justify-content: center; 22 | ` 23 | 24 | const UnaurhorizedState = ({ title, description }) => ( 25 | 26 | 27 | 28 | {title} 29 | 30 | 31 | {description} 32 | 33 | 34 | ) 35 | 36 | UnaurhorizedState.propTypes = { 37 | title: PropTypes.string, 38 | description: PropTypes.string, 39 | } 40 | 41 | UnaurhorizedState.defaultProps = { 42 | title: 'Forbidden', 43 | description: `Oops! It seems that you don't have enough permissions to access this resource`, 44 | } 45 | 46 | export default UnaurhorizedState 47 | -------------------------------------------------------------------------------- /ui/src/containers/footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { grid, space, color } from 'styled-system' 4 | 5 | const Container = styled.div` 6 | background: white; 7 | border-top: 1px solid #f1f1f1; 8 | 9 | display: flex; 10 | justify-content: center; 11 | 12 | align-items: center; 13 | * + * { 14 | margin-left: 12px; 15 | } 16 | ${space} 17 | ${grid} 18 | 19 | z-index: 99; 20 | ` 21 | 22 | const StyledLink = styled.a` 23 | font-size: 14px; 24 | text-decoration: none; 25 | &:hover { 26 | color: ${(props) => props.theme.colors.primary}; 27 | } 28 | ${color} 29 | ` 30 | 31 | StyledLink.defaultProps = { 32 | color: 'neutralDarker', 33 | } 34 | 35 | const Footer = (props) => ( 36 | 37 | Support 38 | Docs 39 | 40 | ) 41 | 42 | Footer.defaultProps = { 43 | padding: 0, 44 | } 45 | 46 | export default Footer 47 | -------------------------------------------------------------------------------- /ui/src/containers/header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { grid, space, color, shadow, border } from 'styled-system' 4 | 5 | import Brand from '_containers/side-nav/brand' 6 | import Flex from '_components/flex' 7 | import Link from '_components/link' 8 | 9 | export const Container = styled.div` 10 | display: flex; 11 | 12 | align-items: center; 13 | justify-content: space-between; 14 | 15 | border-bottom: 1px solid ${(props) => props.theme.colors.neutralLighter}; 16 | position: fixed; 17 | 18 | top: 0; 19 | right: 0; 20 | left: 0; 21 | 22 | z-index: 199; 23 | 24 | ${border} 25 | ${shadow} 26 | ${color} 27 | ${space} 28 | ${grid} 29 | ` 30 | 31 | Container.defaultProps = { 32 | backgroundColor: 'white', 33 | boxShadow: 'light', 34 | border: 'dark', 35 | } 36 | 37 | const StyledLink = styled(Link)` 38 | margin: auto; 39 | padding-right: 18px; 40 | :hover { 41 | color: ${({ theme }) => theme.colors.neutralDarker}; 42 | } 43 | ` 44 | 45 | const Header = (props) => ( 46 | 47 | 48 | 49 | 50 | ACL Tokens 51 | 52 | 53 | 54 | ) 55 | 56 | Header.defaultProps = { 57 | padding: 3, 58 | } 59 | 60 | export default Header 61 | -------------------------------------------------------------------------------- /ui/src/containers/side-nav/brand.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { colorStyle } from 'styled-system' 4 | import { Link } from '@reach/router' 5 | import { ReactComponent as Logo } from '_assets/icons/logo.svg' 6 | 7 | const StyledLink = styled(Link)` 8 | position: relative; 9 | 10 | height: auto; 11 | 12 | display: flex; 13 | align-items: center; 14 | 15 | svg { 16 | fill: ${(props) => props.theme.colors.primary}; 17 | opacity: 0.3; 18 | } 19 | &:hover { 20 | svg { 21 | opacity: 1; 22 | transition: all 0.4s linear; 23 | } 24 | } 25 | ${colorStyle} 26 | ` 27 | 28 | const Brand = (props) => ( 29 | 30 | 31 | 32 | ) 33 | 34 | export default Brand 35 | -------------------------------------------------------------------------------- /ui/src/containers/side-nav/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CollapsibleSection, Container, NavLink } from './styled' 3 | 4 | const SideNav = (props) => ( 5 | 6 | 7 | Networks 8 | Clients 9 | 10 | 11 | ) 12 | 13 | SideNav.defaultProps = { 14 | colors: 'darkHighContrast', 15 | } 16 | 17 | export default SideNav 18 | -------------------------------------------------------------------------------- /ui/src/environment.js: -------------------------------------------------------------------------------- 1 | export const DEBUG = process.env.REACT_APP_DEBUG || false 2 | 3 | export const REST_API_URL = process.env.REACT_APP_REST_API_URL || 'http://localhost:8080' 4 | -------------------------------------------------------------------------------- /ui/src/graphql/local-state.js: -------------------------------------------------------------------------------- 1 | const defaults = { 2 | data: { 3 | toggleState: true, 4 | }, 5 | } 6 | 7 | const resolvers = { 8 | Query: {}, 9 | Mutation: {}, 10 | } 11 | 12 | export { defaults, resolvers } 13 | -------------------------------------------------------------------------------- /ui/src/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from '@reach/router' 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import styled, { ThemeProvider } from 'styled-components' 5 | import { BaseModalBackground, ModalProvider } from 'styled-react-modal' 6 | import ConfirmationDialogProvider from '_components/confirmation-dialog' 7 | import ApolloProvider from '_graphql/apollo-provider' 8 | import ToastProvider from '_utils/toast-provider' 9 | import App from '_views/app' 10 | import NotFound from '_views/not-found' 11 | import * as serviceWorker from './serviceWorker' 12 | import { GlobalStyles, themes } from './styles' 13 | import './assets/fonts/index.css' 14 | 15 | const theme = 'light' 16 | 17 | const ModalBackground = styled(BaseModalBackground)` 18 | z-index: 999; 19 | ` 20 | 21 | ReactDOM.render( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | , 37 | document.getElementById('root') 38 | ) 39 | 40 | serviceWorker.unregister() 41 | -------------------------------------------------------------------------------- /ui/src/mock/commands.js: -------------------------------------------------------------------------------- 1 | import { handler } from './util' 2 | 3 | export default { 4 | createNetwork: handler(() => null), 5 | deleteNetwork: handler(() => null), 6 | } 7 | -------------------------------------------------------------------------------- /ui/src/mock/factories.js: -------------------------------------------------------------------------------- 1 | import faker from 'faker' 2 | import { Factory, trait } from 'miragejs' 3 | 4 | export default { 5 | token: Factory.extend({ 6 | withTime: trait({ 7 | createdAt: () => faker.date.recent(), 8 | updatedAt: () => faker.date.recent(), 9 | }), 10 | }), 11 | network: Factory.extend({ 12 | withTime: trait({ 13 | createdAt: () => faker.date.recent(), 14 | updatedAt: () => faker.date.recent(), 15 | }), 16 | }), 17 | node: Factory.extend({ 18 | withTime: trait({ 19 | createdAt: () => faker.date.recent(), 20 | updatedAt: () => faker.date.recent(), 21 | }), 22 | }), 23 | interface: Factory.extend({ 24 | withTime: trait({ 25 | createdAt: () => faker.date.recent(), 26 | updatedAt: () => faker.date.recent(), 27 | }), 28 | }), 29 | connection: Factory.extend({ 30 | withTime: trait({ 31 | createdAt: () => faker.date.recent(), 32 | updatedAt: () => faker.date.recent(), 33 | }), 34 | }), 35 | } 36 | -------------------------------------------------------------------------------- /ui/src/mock/identity.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid' 2 | 3 | export default class { 4 | constructor() { 5 | this.ids = new Set() 6 | } 7 | 8 | // Returns a new unused unique identifier. 9 | fetch() { 10 | let uuid = uuidv4() 11 | while (this.ids.has(uuid)) { 12 | uuid = uuidv4() 13 | } 14 | 15 | this.ids.add(uuid) 16 | 17 | return uuid 18 | } 19 | 20 | // Registers an identifier as used. Must throw if identifier is already used. 21 | set(id) { 22 | if (this.ids.has(id)) { 23 | throw new Error(`ID ${id} has already been used.`) 24 | } 25 | 26 | this.ids.add(id) 27 | } 28 | 29 | // Resets all used identifiers to unused. 30 | reset() { 31 | this.ids.clear() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ui/src/mock/index.js: -------------------------------------------------------------------------------- 1 | import { createServer, RestSerializer } from 'miragejs' 2 | 3 | import seeds from './seeds' 4 | import models from './models' 5 | import routes from './routes' 6 | import factories from './factories' 7 | 8 | import IdentityManager from './identity' 9 | 10 | export default () => { 11 | createServer({ 12 | identityManagers: { 13 | application: IdentityManager, 14 | }, 15 | serializers: { 16 | application: RestSerializer, 17 | }, 18 | models, 19 | seeds, 20 | routes() { 21 | routes(this) 22 | }, 23 | factories, 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /ui/src/mock/models.js: -------------------------------------------------------------------------------- 1 | import { Model, belongsTo, hasMany } from 'miragejs' 2 | 3 | export default { 4 | networks: Model.extend({}), 5 | 6 | nodes: Model.extend({ 7 | interfaces: hasMany('interface'), 8 | }), 9 | 10 | interfaces: Model.extend({ 11 | node: belongsTo('node'), 12 | network: belongsTo('network'), 13 | connections: hasMany('connection'), 14 | }), 15 | 16 | connections: Model.extend({ 17 | from: belongsTo('interface'), 18 | to: belongsTo('interface'), 19 | }), 20 | } 21 | -------------------------------------------------------------------------------- /ui/src/mock/queries.js: -------------------------------------------------------------------------------- 1 | import { handler } from './util' 2 | 3 | export default { 4 | getSelf: handler((schema, request, self) => self.user.attrs), 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/mock/routes.js: -------------------------------------------------------------------------------- 1 | import { REST_API_URL } from '../environment' 2 | import C from './commands' 3 | import Q from './queries' 4 | 5 | const routes = (server) => { 6 | const get = (path, handler) => server.get(`${REST_API_URL}${path}`, handler, { timing: 1000 }) 7 | const post = (path, handler) => server.post(`${REST_API_URL}${path}`, handler, { timing: 2000 }) 8 | 9 | // Setup query routes 10 | get('/api/self/token', Q.getSelf) 11 | 12 | // Setup command routes 13 | post('/api/networks/', C.createNetwork) 14 | 15 | // Setup passthrough routes (to which requests will not be intercepted by Mirage) 16 | // server.passthrough() 17 | } 18 | 19 | export default routes 20 | -------------------------------------------------------------------------------- /ui/src/mock/seeds.js: -------------------------------------------------------------------------------- 1 | export default (server) => { 2 | const create = (...args) => server.create(...args, 'withTime') 3 | const networks = { 4 | n1: create('network', { name: 'net1' }), 5 | n2: create('network', { name: 'net2' }), 6 | } 7 | const nodes = { 8 | n1: create('node', { name: 'node1' }), 9 | n2: create('node', { name: 'node2' }), 10 | } 11 | const interfaces = { 12 | i1: create('interface', { name: 'wg0', node: nodes.n1, network: networks.n1 }), 13 | i2: create('interface', { name: 'wg0', node: nodes.n2, network: networks.n2 }), 14 | } 15 | // eslint-disable-next-line no-unused-vars 16 | const connections = { 17 | c1: create('interface', { from: interfaces.i1, to: interfaces.i2 }), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ui/src/mock/util.js: -------------------------------------------------------------------------------- 1 | export const handler = (f) => (schema, request) => { 2 | const context = {} 3 | const self = {} 4 | request.body = JSON.parse(request.requestBody) 5 | request.headers = request.requestHeaders 6 | return f(schema, request, self, context) 7 | } 8 | 9 | export default {} 10 | -------------------------------------------------------------------------------- /ui/src/styles/index.js: -------------------------------------------------------------------------------- 1 | import reset from 'styled-reset' 2 | import { variant } from 'styled-system' 3 | import { createGlobalStyle } from 'styled-components' 4 | 5 | import lightTheme from './themes/light' 6 | 7 | const GlobalStyles = createGlobalStyle` 8 | ${reset} 9 | 10 | :root { 11 | height: 100%; 12 | font-family: Lato; 13 | text-rendering: optimizeLegibility; 14 | outline: none; 15 | } 16 | 17 | body { 18 | height: 100%; 19 | } 20 | 21 | body::-webkit-scrollbar { 22 | width: 8px; 23 | height: 8px; 24 | background-color: ${(props) => props.theme.colors.background3}; 25 | } 26 | 27 | body::-webkit-scrollbar-track { 28 | border-radius: 4px; 29 | background-color: ${(props) => props.theme.colors.background3}; 30 | } 31 | 32 | body::-webkit-scrollbar-thumb { 33 | border-radius: 4px; 34 | background-color: ${(props) => props.theme.colors.border1}; 35 | } 36 | 37 | * { 38 | outline: none; 39 | -webkit-tap-highlight-color: transparent; 40 | } 41 | 42 | button { 43 | cursor: pointer; 44 | } 45 | ` 46 | 47 | const containers = variant({ 48 | scale: 'containers', 49 | prop: 'type', 50 | }) 51 | 52 | const themes = { light: lightTheme } 53 | 54 | export { GlobalStyles, containers, themes } 55 | -------------------------------------------------------------------------------- /ui/src/utils/formik-utils.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { Portal } from 'react-portal' 4 | import Box from '_components/box' 5 | import { DEBUG } from '../environment' 6 | 7 | const Container = styled(Box).attrs({ 8 | border: 'discrete', 9 | })` 10 | padding: 12px; 11 | position: absolute; 12 | bottom: 40px; 13 | left: 20px; 14 | height: auto; 15 | ` 16 | 17 | const FormikState = (props) => 18 | DEBUG ? ( 19 | 20 | 21 |
22 |
32 |             {JSON.stringify(props, null, 4)}
33 |           
34 |
35 |
36 |
37 | ) : null 38 | 39 | export default FormikState 40 | -------------------------------------------------------------------------------- /ui/src/utils/hocs/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/prefer-default-export 2 | export { default as withValidityIndicator } from './with-validity-indicator' 3 | -------------------------------------------------------------------------------- /ui/src/utils/hocs/with-validity-indicator.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import { icons } from '_assets/' 5 | import Box from '_components/box' 6 | import Popover from '_components/popover' 7 | import Text from '_components/text' 8 | 9 | const ErrorIconContainer = styled.div` 10 | position: absolute; 11 | right: -28px; 12 | top: 50%; 13 | transform: translateY(-50%); 14 | ` 15 | 16 | const ErrorTooltip = styled.div` 17 | padding: 8px; 18 | max-width: 180px; 19 | background: #fff; 20 | border-radius: 4px; 21 | ` 22 | 23 | const ErrorIndicator = ({ error }) => { 24 | if (error === undefined) return null 25 | return ( 26 | 27 | 31 | 32 | {error} 33 | 34 | 35 | } 36 | > 37 | 38 | 39 | 40 | ) 41 | } 42 | 43 | ErrorIndicator.propTypes = { 44 | error: PropTypes.string, 45 | } 46 | 47 | ErrorIndicator.defaultProps = { 48 | error: undefined, 49 | } 50 | 51 | const withValidityIndicator = (input, error) => ( 52 | 53 | {input} 54 | 55 | 56 | ) 57 | 58 | export default withValidityIndicator 59 | -------------------------------------------------------------------------------- /ui/src/utils/toast-provider.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React, { useContext } from 'react' 3 | import { toast } from 'react-toastify' 4 | 5 | import { icons } from '_assets/' 6 | 7 | import { 8 | ToastContainer, 9 | ToastBody, 10 | IconContainer, 11 | DismissButton, 12 | ToastContent, 13 | } from '_components/toast' 14 | 15 | const displayToast = (content, icon, color, options = {}) => { 16 | toast( 17 | ({ closeToast }) => ( 18 | 19 | {icon} 20 | {content} 21 | } color={color} onClick={() => closeToast()} /> 22 | 23 | ), 24 | { ...options, autoClose: 3000, closeButton: false } 25 | ) 26 | } 27 | 28 | const ToastContext = React.createContext() 29 | 30 | export const useToast = () => useContext(ToastContext) 31 | 32 | const ToastProvider = ({ children }) => ( 33 | <> 34 | displayToast(text, icon, color, options), 37 | error: (text, options) => displayToast(text, , 'danger', options), 38 | warning: (text, options) => displayToast(text, , 'warning', options), 39 | success: (text, options) => displayToast(text, , 'success', options), 40 | }} 41 | > 42 | {children} 43 | 44 | 45 | 46 | ) 47 | 48 | ToastProvider.propTypes = { 49 | children: PropTypes.node.isRequired, 50 | } 51 | 52 | export default ToastProvider 53 | -------------------------------------------------------------------------------- /ui/src/views/app/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from '@reach/router' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import Footer from '_containers/footer' 5 | import Header from '_containers/header' 6 | import SideNav from '_containers/side-nav' 7 | import ClientsRouter from '_views/clients' 8 | import HomeView from '_views/home' 9 | import NetworksRouter from '_views/networks' 10 | import NotFound from '_views/not-found' 11 | import SettingsRouter from '_views/settings' 12 | 13 | const Dashboard = styled.div` 14 | position: relative; 15 | display: grid; 16 | height: 100vh; 17 | grid-template: 72px auto 40px / auto; 18 | grid-template-areas: 19 | 'header' 20 | 'body' 21 | 'footer'; 22 | ` 23 | 24 | const Content = styled(Router).attrs({ primary: false })` 25 | padding-top: 84px; 26 | padding-bottom: 32px; 27 | 28 | grid-area: body; 29 | 30 | width: 90%; 31 | max-width: 800px; 32 | justify-self: center; 33 | 34 | // Laptops and above 35 | @media (min-width: 1280px) { 36 | padding-left: 200px; 37 | } 38 | ` 39 | 40 | const App = () => ( 41 | 42 |
43 | 44 | 45 | 46 | 47 | {/* */} 48 | {/* */} 49 | 50 | 51 | 52 | 53 |