├── .github
├── dependabot.yml
└── workflows
│ ├── docker-builder.yml
│ ├── publish-docker.yml
│ └── release.yml
├── .gitignore
├── .prettierrc
├── Dockerfile
├── LICENSE.txt
├── README.md
├── config-overrides.js
├── docker-entrypoint.sh
├── docker
└── dockerfile.builder
├── generate_config_js.sh
├── licensing
└── LICENSE.txt
├── netmaker.png
├── nginx.conf
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192192.png
├── logo256256.png
├── logo512512.png
├── manifest.json
└── robots.txt
├── release.md
├── src
├── App.css
├── App.test.js
├── App.tsx
├── NM.png
├── components
│ ├── Checkbox.ts
│ ├── ConfirmDialog.tsx
│ ├── CopyText.tsx
│ ├── CustomSelect.tsx
│ ├── Link.tsx
│ ├── Loading.tsx
│ ├── LoadingText.tsx
│ ├── NetworkSelect.tsx
│ ├── PathBreadcrumbs.tsx
│ ├── Table.tsx
│ ├── dashboard
│ │ ├── ACLCard.tsx
│ │ ├── AccessKeyCard.tsx
│ │ ├── AdminCard.tsx
│ │ ├── DNSCard.tsx
│ │ ├── EnrollmentKeyCard.tsx
│ │ ├── ExtClientsCard.tsx
│ │ ├── GraphCard.tsx
│ │ ├── NetworkCard.tsx
│ │ ├── NodeCard.tsx
│ │ └── UserCard.tsx
│ ├── dialog
│ │ └── CustomDialog.tsx
│ ├── drawer
│ │ └── CustomDrawer.tsx
│ ├── filter
│ │ └── Tablefilter.tsx
│ ├── form
│ │ ├── Form.tsx
│ │ ├── FormCheckboxInput.tsx
│ │ ├── FormList.tsx
│ │ ├── FormOptionSelect.tsx
│ │ ├── FormSwitchInput.tsx
│ │ ├── FormTextInput.tsx
│ │ ├── index.ts
│ │ ├── internal
│ │ │ └── formContext.ts
│ │ └── validate.ts
│ ├── index.ts
│ ├── loading-text.css
│ ├── theme.ts
│ └── utils.tsx
├── config.ts
├── ee
│ ├── LICENSE
│ ├── metrics
│ │ ├── MetricRoute.tsx
│ │ ├── components
│ │ │ └── MetricButton.tsx
│ │ ├── util.tsx
│ │ └── views
│ │ │ ├── ExtMetrics.tsx
│ │ │ ├── MetricsTable.tsx
│ │ │ ├── NodeMetrics.tsx
│ │ │ └── charts
│ │ │ ├── ReceivedChart.tsx
│ │ │ ├── SentChart.tsx
│ │ │ └── Uptime.tsx
│ ├── nodes
│ │ └── FailoverButton.tsx
│ └── users
│ │ ├── UsersEE.tsx
│ │ └── components
│ │ ├── UserEditEE.tsx
│ │ ├── UserGroupEdit.tsx
│ │ └── UserTableEE.tsx
├── i18n
│ ├── i18n.ts
│ ├── resources
│ │ └── en.ts
│ └── type.ts
├── index.css
├── index.tsx
├── logo.svg
├── netmaker-logo-2.png
├── netmaker-logo.png
├── proroute
│ ├── proaccesskeys
│ │ ├── ProDashboardAccessKeys.tsx
│ │ └── components
│ │ │ ├── ProAccessKeyCreate.tsx
│ │ │ ├── ProAccessKeyTable.tsx
│ │ │ └── ProAccessKeyView.tsx
│ ├── proaccessleveldashboards
│ │ ├── NetUserSwitch.tsx
│ │ └── components
│ │ │ ├── AdminNetwork.tsx
│ │ │ ├── ExtAccessCard.tsx
│ │ │ ├── NodeAccessCard.tsx
│ │ │ ├── ProAccessKeyCard.tsx
│ │ │ └── WelcomeCard.tsx
│ ├── prodrawer
│ │ └── ProDrawerNotAdmin.tsx
│ ├── prologin
│ │ ├── CreateAdmin.tsx
│ │ └── Login.tsx
│ ├── pronetadmin
│ │ ├── NetAdminDashboard.tsx
│ │ ├── components
│ │ │ ├── ProChoicesSelect.tsx
│ │ │ ├── ProNetworkCreate.tsx
│ │ │ ├── ProNetworkEdit.tsx
│ │ │ ├── ProNetworkListEdit.tsx
│ │ │ └── ProNetworkModifiedStats.tsx
│ │ └── views
│ │ │ └── ProNetworkTable.tsx
│ ├── pronodeuser
│ │ ├── NodeUserDashboard.tsx
│ │ ├── components
│ │ │ ├── NodeAccessView.tsx
│ │ │ ├── ProNodeEdit.tsx
│ │ │ ├── ProNodeId.tsx
│ │ │ └── ProNodesView.tsx
│ │ └── views
│ │ │ ├── ProNodeEdit.tsx
│ │ │ └── RemoteAccessView.tsx
│ ├── prouser
│ │ ├── ProUserView.tsx
│ │ ├── VpnDashboard.tsx
│ │ └── components
│ │ │ ├── DeleteExtClientButtonVpn.tsx
│ │ │ ├── DownloadExtClientButtonVpn.tsx
│ │ │ ├── EditExtClientButtonVpn.tsx
│ │ │ ├── ExtClientCreateButtonVpn.tsx
│ │ │ ├── ExtClientEditVpn.tsx
│ │ │ ├── ExtClientViewVpn.tsx
│ │ │ ├── NetClientsViewVpn.tsx
│ │ │ ├── NetworkSelectVpn.tsx
│ │ │ ├── QrCodeViewVpn.tsx
│ │ │ └── testdata.tsx
│ └── proutils
│ │ ├── ProConsts.tsx
│ │ ├── ProCustomSelect.tsx
│ │ └── ProNetworkSelect.tsx
├── react-app-env.d.ts
├── route
│ ├── Header.tsx
│ ├── PrivateRoute.tsx
│ ├── accesskeys
│ │ ├── AccessKeys.tsx
│ │ └── components
│ │ │ ├── AccessKeyCreate.tsx
│ │ │ ├── AccessKeyDetails.tsx
│ │ │ ├── AccessKeySelect.tsx
│ │ │ ├── AccessKeyTable.tsx
│ │ │ └── AccessKeyView.tsx
│ ├── dashboard
│ │ ├── Dashboard.tsx
│ │ └── dashboard-styles.css
│ ├── dns
│ │ ├── DNS.tsx
│ │ └── components
│ │ │ ├── DNSCreate.tsx
│ │ │ ├── DNSSelect.tsx
│ │ │ └── DNSView.tsx
│ ├── enrollmentkeys
│ │ ├── EnrollmentKeysPage.tsx
│ │ └── components
│ │ │ ├── CreateEnrollmentKeyModal.tsx
│ │ │ ├── EnrollmentKeyDetailsModal.tsx
│ │ │ └── EnrollmentKeysTable.tsx
│ ├── extclients
│ │ ├── ExtClients.tsx
│ │ └── components
│ │ │ ├── DeleteExtClientButton.tsx
│ │ │ ├── DownloadExtClientButton.tsx
│ │ │ ├── EditExtClientButton.tsx
│ │ │ ├── ExtClientCreateButton.tsx
│ │ │ ├── ExtClientEdit.tsx
│ │ │ ├── ExtClientView.tsx
│ │ │ ├── NetClientsView.tsx
│ │ │ ├── NetworkSelect.tsx
│ │ │ └── QrCodeView.tsx
│ ├── graph
│ │ ├── Graphs.tsx
│ │ └── components
│ │ │ ├── NetworkGraph.tsx
│ │ │ └── graph-components
│ │ │ ├── NodeDetails.tsx
│ │ │ ├── NodeGraph.tsx
│ │ │ ├── types.tsx
│ │ │ └── util.tsx
│ ├── hosts
│ │ ├── HostDetailPage.tsx
│ │ ├── HostEditPage.tsx
│ │ ├── HostsPage.tsx
│ │ └── components
│ │ │ ├── CreateRelayModal.tsx
│ │ │ ├── HostNetworksTable.tsx
│ │ │ ├── HostRelayTable.tsx
│ │ │ └── HostsTable.tsx
│ ├── login
│ │ └── Login.tsx
│ ├── logs
│ │ └── ServerLogs.tsx
│ ├── networks
│ │ ├── Networks.tsx
│ │ ├── components
│ │ │ └── NetworkTable.tsx
│ │ ├── create
│ │ │ └── NetworkCreate.tsx
│ │ └── networkId
│ │ │ ├── NetworkId.tsx
│ │ │ ├── accesskeys
│ │ │ └── AccessKeys.tsx
│ │ │ ├── components
│ │ │ └── NetworkModifiedStats.tsx
│ │ │ └── edit
│ │ │ ├── ChoicesSelect.tsx
│ │ │ ├── NetworkEdit.tsx
│ │ │ └── NetworkListEdit.tsx
│ ├── networkusers
│ │ ├── NetworkUsers.tsx
│ │ └── components
│ │ │ ├── ChoicesSelect.tsx
│ │ │ ├── NetworkUserEdit.tsx
│ │ │ └── NetworkUsersTable.tsx
│ ├── node_acls
│ │ ├── NodeACLs.tsx
│ │ └── components
│ │ │ └── NodesACLTable.tsx
│ ├── nodes
│ │ ├── Nodes.tsx
│ │ ├── components
│ │ │ └── NodeTable.tsx
│ │ └── netid
│ │ │ ├── NetworkNodes.tsx
│ │ │ ├── components
│ │ │ ├── CreateEgress.tsx
│ │ │ ├── CreateRelay.tsx
│ │ │ ├── HubButton.tsx
│ │ │ ├── RelaySelect.tsx
│ │ │ └── TableToggleButton.tsx
│ │ │ ├── nodeEdit
│ │ │ └── NodeEdit.tsx
│ │ │ └── nodeId
│ │ │ └── NodeId.tsx
│ ├── root.tsx
│ ├── usergroups
│ │ ├── UserGroups.tsx
│ │ ├── create
│ │ │ └── UserGroupCreate.tsx
│ │ └── table
│ │ │ └── UserGroupsTable.tsx
│ └── users
│ │ ├── UsersCommunity.tsx
│ │ ├── components
│ │ └── UserTableCommunity.tsx
│ │ ├── create
│ │ └── UserCreate.tsx
│ │ └── userId
│ │ ├── UserChangePassword.tsx
│ │ └── UserEditCommunity.tsx
├── serviceWorker.js
├── setupTests.js
├── store
│ ├── actions.ts
│ ├── createStore.ts
│ ├── modules
│ │ ├── acls
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ └── types.ts
│ │ ├── api
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ └── types.ts
│ │ ├── auth
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ └── types.ts
│ │ ├── enrollmentkeys
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ └── types.ts
│ │ ├── hosts
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ └── types.ts
│ │ ├── network
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── node
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── pro
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── router
│ │ │ ├── Component.tsx
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ └── types.ts
│ │ ├── server
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── reducer.ts
│ │ │ ├── saga.ts
│ │ │ ├── selectors.ts
│ │ │ └── types.ts
│ │ └── toast
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ ├── saga.ts
│ │ │ └── types.ts
│ ├── reducers.ts
│ ├── sagas.ts
│ ├── selectors.ts
│ └── types.ts
├── types
│ └── react-app-env.d.ts
└── util
│ ├── enrollmentkeys.ts
│ ├── errorpage.tsx
│ ├── fields.ts
│ ├── genericerror.tsx
│ ├── hosts.ts
│ ├── network.ts
│ ├── node.ts
│ ├── query.ts
│ ├── regex.ts
│ └── unixTime.ts
├── tsconfig.json
└── tsconfig.paths.json
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | target-branch: "develop"
8 | - package-ecosystem: "github-actions"
9 | directory: "/"
10 | schedule:
11 | interval: "weekly"
12 | target-branch: "develop"
13 | - package-ecosystem: "docker"
14 | directory: "/"
15 | schedule:
16 | interval: "weekly"
17 | target-branch: "develop"
18 |
--------------------------------------------------------------------------------
/.github/workflows/docker-builder.yml:
--------------------------------------------------------------------------------
1 | name: Build go-builder images
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '00 21 * * SUN'
7 |
8 | jobs:
9 | go-builder:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v2
14 | - name: SetUp Buildx
15 | uses: docker/setup-buildx-action@v1
16 | - name: Login to Dockerhub
17 | uses: docker/login-action@v1
18 | with:
19 | username: ${{ secrets.DOCKERHUB_USERNAME }}
20 | password: ${{ secrets.DOCKERHUB_TOKEN }}
21 | - name: Build and push to docker hub
22 | uses: docker/build-push-action@v2
23 | with:
24 | context: .
25 | push: true
26 | platforms: linux/amd64, linux/arm64
27 | file: ./docker/dockerfile.builder
28 | tags: gravitl/go-ui-builder:latest
--------------------------------------------------------------------------------
/.github/workflows/publish-docker.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | tag:
7 | description: 'docker tag'
8 | required: true
9 | release:
10 | types: [published]
11 |
12 | jobs:
13 | docker:
14 | runs-on: ubuntu-latest
15 | steps:
16 | -
17 | name: Set tag
18 | run: |
19 | if [[ -n "${{ github.event.inputs.tag }}" ]]; then
20 | TAG=${{ github.event.inputs.tag }}
21 | elif [[ "${{ github.ref_name }}" == 'master' ]]; then
22 | TAG="latest"
23 | else
24 | TAG="${{ github.ref_name }}"
25 | fi
26 | echo "TAG=${TAG}" >> $GITHUB_ENV
27 | -
28 | name: Checkout
29 | uses: actions/checkout@v3
30 | -
31 | name: Set up QEMU
32 | uses: docker/setup-qemu-action@v2
33 | -
34 | name: Set up Docker Buildx
35 | uses: docker/setup-buildx-action@v2
36 | -
37 | name: Login to DockerHub
38 | uses: docker/login-action@v2
39 | with:
40 | username: ${{ secrets.DOCKERHUB_USERNAME }}
41 | password: ${{ secrets.DOCKERHUB_TOKEN }}
42 | -
43 | name: Build and push
44 | uses: docker/build-push-action@v4
45 | with:
46 | context: .
47 | platforms: linux/amd64, linux/arm64, linux/arm/v7
48 | push: true
49 | tags: ${{ github.repository }}:${{ env.TAG }}, ${{ github.repository }}:latest
50 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | release:
2 | runs-on: ubuntu-latest
3 | permissions:
4 | contents: write
5 | steps:
6 | - name: checkout
7 | uses: actions/checkout@v3
8 | with:
9 | ref: develop
10 | - name: setup git
11 | run: |
12 | git config user.name "Github Actions"
13 | git config user.email "info@netmaker.io"
14 | - name: create branch
15 | git switch -c release_${{ inputs.version }}
16 | git tag -f ${{ inputs.version }}
17 | #push branch
18 | git push origin release_${{ inputs.version }}
19 | #push tag
20 | git push origin ${{ inputs.version }}
21 |
22 | - name: release assets
23 | uses: ncipollo/release-action@v1
24 | with:
25 | bodyFile: "release.md"
26 | name: ${{ inputs.version }}
27 | tag: ${{ inputs.version }}
28 | commit: develop
29 | - name: pull request
30 | env:
31 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 | run: |
33 | gh api --method POST \
34 | -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' \
35 | /repos/${{ github.repository }}/pulls \
36 | -f title='${{ inputs.version }}' \
37 | -f head='release_${{ inputs.version }}' \
38 | -f base="master"
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | .vscode/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "semi": false,
5 | "singleQuote": true,
6 | "trailingComma": "es5"
7 | }
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:17.1.0 as builder
2 |
3 | LABEL \
4 | org.opencontainers.image.authors="Dillon Carns & Alex Feiszli, Gravitl, inc." \
5 | org.opencontainers.image.vendor="ReactJS" \
6 | org.opencontainers.image.url="local" \
7 | org.opencontainers.image.source="https://dockerhub.com/" \
8 | org.opencontainers.image.version="$VERSION" \
9 | org.opencontainers.image.revision="$REVISION" \
10 | vendor="ReactJS" \
11 | name="Netmaker" \
12 | version="$VERSION-$REVISION" \
13 | summary="The frontend of Netmaker. Netmaker builds fast, secure virtual networks." \
14 | description="This image contains the Netmaker frontend running with the ReactJS runtime. 2021 - Gravitl, inc."
15 |
16 | RUN mkdir /usr/src/app
17 | WORKDIR /usr/src/app
18 | ENV PATH /usr/src/app/node_modules/.bin:$PATH
19 | COPY package*.json ./
20 | RUN npm install --silent
21 | RUN npm install react-scripts@4.0.3 -g --silent
22 | COPY . /usr/src/app
23 | ENV NODE_OPTIONS "--openssl-legacy-provider"
24 | RUN npm run build
25 |
26 | FROM nginx:1.23-alpine
27 | # RUN rm -rf /etc/nginx/conf.d
28 | COPY nginx.conf /etc/nginx/conf.d/default.conf
29 | COPY --from=builder /usr/src/app/build /usr/share/nginx/html
30 | COPY docker-entrypoint.sh generate_config_js.sh /
31 | RUN chmod +x docker-entrypoint.sh generate_config_js.sh
32 | ENTRYPOINT ["/docker-entrypoint.sh"]
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## v0.19.0
6 |
7 | ## For more information, visit one of the official documentation sites:
8 |
9 | 1. [docs.netmaker.org](https://docs.netmaker.org)
10 | 2. [netmaker.readthedocs.io](https://netmaker.readthedocs.io)
11 |
12 | ## To run dev server:
13 |
14 | 1. `npm i`
15 | 2. `npm start` or `NODE_OPTIONS=--openssl-legacy-provider npm start` for newer versions of node due [this bug](https://github.com/webpack/webpack/issues/14532) in webpack
16 |
17 | Runs the app in the development mode.
18 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
19 |
20 | The page will reload if you make edits.
21 | You will also see any lint errors in the console.
22 |
23 | ## To build static files:
24 |
25 | 1. `npm i`
26 | 2. `npm run build`
27 |
28 | #### LICENSE
29 |
30 | Netmaker UI's source code and all artifacts in this repository are freely available. All versions are published under the Server Side Public License (SSPL), version 1, which can be found under the "licensing" directory: [LICENSE.txt](licensing/LICENSE.txt).
31 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const {alias, configPaths} = require('react-app-rewire-alias')
2 |
3 | module.exports = function override(config) {
4 | return alias(configPaths('./tsconfig.paths.json'))(config)
5 | };
6 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -eu
2 | ./generate_config_js.sh >/usr/share/nginx/html/config.js
3 | echo ">>>> backend set to: $BACKEND_URL <<<<<"
4 | nginx -g "daemon off;"
5 |
--------------------------------------------------------------------------------
/docker/dockerfile.builder:
--------------------------------------------------------------------------------
1 | FROM node:17.1.0
2 |
3 | LABEL \
4 | org.opencontainers.image.authors="Dillon Carns & Alex Feiszli, Gravitl, inc." \
5 | org.opencontainers.image.vendor="ReactJS" \
6 | org.opencontainers.image.url="local" \
7 | org.opencontainers.image.source="https://dockerhub.com/" \
8 | org.opencontainers.image.version="$VERSION" \
9 | org.opencontainers.image.revision="$REVISION" \
10 | vendor="ReactJS" \
11 | name="Netmaker" \
12 | version="$VERSION-$REVISION" \
13 | summary="The frontend of Netmaker. Netmaker builds fast, secure virtual networks." \
14 | description="This image contains the Netmaker frontend running with the ReactJS runtime. 2021 - Gravitl, inc."
15 |
16 | RUN mkdir /usr/src/app
17 | WORKDIR /usr/src/app
18 | ENV PATH /usr/src/app/node_modules/.bin:$PATH
19 | COPY package*.json ./
20 | RUN npm install --silent
21 | RUN npm install react-scripts@4.0.3 -g --silent
--------------------------------------------------------------------------------
/generate_config_js.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -eu
2 | if [ -z "${BACKEND_URL:-}" ]; then
3 | BACKEND_URL="http://localhost:8081"
4 | fi
5 |
6 | cat <0.2%",
62 | "not dead",
63 | "not op_mini all"
64 | ],
65 | "development": [
66 | "last 1 chrome version",
67 | "last 1 firefox version",
68 | "last 1 safari version"
69 | ]
70 | },
71 | "devDependencies": {
72 | "@testing-library/jest-dom": "^4.2.4",
73 | "@testing-library/react": "^9.5.0",
74 | "@testing-library/user-event": "^7.2.1",
75 | "@types/jest": "^27.0.2",
76 | "@types/node": "^18.16.0",
77 | "@types/react": "^17.0.24",
78 | "@types/react-dom": "^17.0.9",
79 | "@types/react-router-dom": "^5.3.0",
80 | "@types/redux-logger": "^3.0.9",
81 | "@welldone-software/why-did-you-render": "^6.2.1",
82 | "prettier": "^2.4.1",
83 | "react-app-rewire-alias": "^1.1.4",
84 | "react-app-rewired": "^2.1.8"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gravitl/netmaker-ui/9083e5e0f8202df166e4b664dded6b401d4fc74f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 |
17 |
18 |
19 | Netmaker
20 |
21 |
22 |
23 |
24 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/public/logo192192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gravitl/netmaker-ui/9083e5e0f8202df166e4b664dded6b401d4fc74f/public/logo192192.png
--------------------------------------------------------------------------------
/public/logo256256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gravitl/netmaker-ui/9083e5e0f8202df166e4b664dded6b401d4fc74f/public/logo256256.png
--------------------------------------------------------------------------------
/public/logo512512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gravitl/netmaker-ui/9083e5e0f8202df166e4b664dded6b401d4fc74f/public/logo512512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Netmaker UI",
3 | "name": "Netmaker Management UI",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/release.md:
--------------------------------------------------------------------------------
1 | # Netmaker-UI v0.19.0
2 |
3 | ## whats new
4 | dependencies updated
5 |
6 | ## whats fixed
7 |
8 | ## known issues
9 |
10 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | body::-webkit-scrollbar {
2 | width: 1em;
3 | }
4 |
5 | body::-webkit-scrollbar-track {
6 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
7 | }
8 |
9 | body::-webkit-scrollbar-thumb {
10 | background-color: darkgrey;
11 | outline: 1px solid slategrey;
12 | }
13 |
14 | .selected {
15 | background-color: transparent;
16 | }
17 |
18 | .selected:hover {
19 | background: #139da4;
20 | }
21 |
22 | .MuiInputBase-fromControl {
23 | height: 50vh;
24 | }
25 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '@testing-library/react'
3 | import App from './App'
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render()
7 | const linkElement = getByText(/learn react/i)
8 | expect(linkElement).toBeInTheDocument()
9 | })
10 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './App.css'
4 | import 'react-toastify/dist/ReactToastify.css'
5 |
6 | import { ToastContainer } from 'react-toastify'
7 | import { ThemeProvider } from '@mui/material/styles'
8 | import { useCurrentTheme } from './components/theme'
9 | import { useSelector, useDispatch } from 'react-redux'
10 | import { authSelectors, nodeSelectors, aclSelectors } from '~store/types'
11 | import { logout } from '~store/modules/auth/actions'
12 | import AdapterDateFns from '@mui/lab/AdapterDateFns'
13 | import LocalizationProvider from '@mui/lab/LocalizationProvider'
14 |
15 | import Routes from './route/root'
16 | import { PathBreadcrumbsProvider } from './components/PathBreadcrumbs'
17 | import { getNodes } from '~store/modules/node/actions'
18 | import { getHosts } from '~store/modules/hosts/actions'
19 |
20 | function App() {
21 | const dispatch = useDispatch()
22 | const user = useSelector(authSelectors.getUser)
23 | const token = useSelector(authSelectors.getToken)
24 | const isLoggedIn = useSelector(authSelectors.getLoggedIn)
25 | const shouldSignOut = useSelector(nodeSelectors.getShouldSignOut)
26 | const currentACL = useSelector(aclSelectors.getCurrentACL)
27 | const userSettings = useSelector(authSelectors.getUserSettings)
28 |
29 | React.useEffect(() => {
30 | const interval = setInterval(() => {
31 |
32 | if (!isLoggedIn || (!!user && Date.now() / 1000 > user.exp && !window.location.href.includes('/login'))) {
33 | dispatch(logout())
34 | } else if (shouldSignOut) {
35 | dispatch(logout())
36 | } else if (isLoggedIn && token && user && user.isAdmin) {
37 | dispatch(getNodes.request({ token }))
38 | dispatch(getHosts.request())
39 | }
40 |
41 | }, 7500)
42 | return () => clearInterval(interval)
43 | }, [dispatch, user, isLoggedIn, token, shouldSignOut, currentACL])
44 |
45 | const theme = useCurrentTheme(userSettings.mode || 'dark')
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | )
59 | }
60 |
61 | export default App
62 |
--------------------------------------------------------------------------------
/src/NM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gravitl/netmaker-ui/9083e5e0f8202df166e4b664dded6b401d4fc74f/src/NM.png
--------------------------------------------------------------------------------
/src/components/Checkbox.ts:
--------------------------------------------------------------------------------
1 | import Checkbox from '@mui/material/Checkbox'
2 | import { styled } from '@mui/material/styles'
3 |
4 | export const NmCheckbox = styled(Checkbox)(({ theme }) => ({
5 | color: theme.status.danger,
6 | '&.Mui-checked': {
7 | color: theme.status.danger,
8 | },
9 | }))
10 |
--------------------------------------------------------------------------------
/src/components/ConfirmDialog.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from 'react'
2 | import {
3 | Dialog,
4 | DialogTitle,
5 | DialogContent,
6 | DialogActions,
7 | Button,
8 | Box,
9 | IconButton,
10 | Typography,
11 | } from '@mui/material'
12 | import { Close } from '@mui/icons-material'
13 | import { useTranslation } from 'react-i18next'
14 | import { TFunctionResult } from 'i18next'
15 |
16 | interface Props {
17 | title?: string
18 | message: string | React.ReactChild | TFunctionResult
19 | onCancel?: () => void
20 | onSubmit: () => void
21 | }
22 |
23 | const ConfirmDialog: React.FC = ({
24 | visible,
25 | title,
26 | message,
27 | onCancel,
28 | onSubmit,
29 | }) => {
30 | const { t } = useTranslation()
31 | return (
32 |
55 | )
56 | }
57 |
58 | export const useDialog = () => {
59 | const [props, setProps] = useState(undefined)
60 |
61 | const onCancel = useCallback(() => {
62 | if (props) {
63 | props.onCancel?.()
64 | setProps(undefined)
65 | }
66 | }, [props, setProps])
67 |
68 | const onSubmit = useCallback(() => {
69 | if (props) {
70 | props.onSubmit()
71 | setProps(undefined)
72 | }
73 | }, [props, setProps])
74 |
75 | if (props) {
76 | return {
77 | Component: () => (
78 |
84 | ),
85 | setProps,
86 | }
87 | }
88 | return {
89 | Component: () => (
90 |
96 | ),
97 | setProps,
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/components/CopyText.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, Tooltip } from '@mui/material'
2 | import Typography from '@mui/material/Typography'
3 | import { TypographyVariant } from '@mui/material'
4 | import copy from 'copy-to-clipboard'
5 | import { Button } from '@mui/material'
6 | import { useTranslation } from 'react-i18next'
7 | import { Fragment } from 'react'
8 |
9 | export interface CopyProps {
10 | value: string
11 | color?: string
12 | type: TypographyVariant
13 | }
14 |
15 | export default function CopyText(Props: CopyProps) {
16 | const { t } = useTranslation()
17 |
18 | return (
19 |
20 | copy(Props.value)}>
21 |
30 |
31 |
32 | )
33 | }
34 |
35 | export interface MulitCopyProps {
36 | color?: string
37 | type: TypographyVariant
38 | values: Array
39 | fullWidth?: boolean
40 | }
41 |
42 | export function MultiCopy({ color, type, values, fullWidth }: MulitCopyProps) {
43 | const isFullWidth = fullWidth === undefined ? true : fullWidth
44 |
45 | return (
46 |
47 | {values.map((value, i) =>
48 | !!value ? (
49 |
50 | {isFullWidth && (
51 |
52 |
53 |
54 | )}
55 | {!isFullWidth && (
56 |
57 | )}
58 |
59 | ) : null
60 | )}
61 |
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/CustomSelect.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Theme, useTheme } from '@mui/material/styles'
3 | import OutlinedInput from '@mui/material/OutlinedInput'
4 | import MenuItem from '@mui/material/MenuItem'
5 | import FormControl from '@mui/material/FormControl'
6 | import Select, { SelectChangeEvent } from '@mui/material/Select'
7 | import { useTranslation } from 'react-i18next'
8 |
9 | const ITEM_HEIGHT = 48
10 | const ITEM_PADDING_TOP = 8
11 | const MenuProps = {
12 | PaperProps: {
13 | style: {
14 | maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
15 | width: 250,
16 | },
17 | },
18 | }
19 |
20 | function getStyles(name: string, personName: readonly string[], theme: Theme) {
21 | return {
22 | fontWeight:
23 | personName.indexOf(name) === -1
24 | ? theme.typography.fontWeightRegular
25 | : theme.typography.fontWeightMedium,
26 | }
27 | }
28 |
29 | export default function CustomSelect(Props: {
30 | items: string[]
31 | onSelect: (s: string) => void
32 | placeholder: string
33 | }) {
34 | const theme = useTheme()
35 | const [selectedItem, setSelectedItem] = React.useState([])
36 | const { t } = useTranslation()
37 | const handleChange = (event: SelectChangeEvent) => {
38 | const {
39 | target: { value },
40 | } = event
41 | setSelectedItem(
42 | // On autofill we get a the stringified value.
43 | typeof value === 'string' ? value.split(',') : value
44 | )
45 | Props.onSelect(value as string)
46 | }
47 |
48 | return (
49 |
50 |
51 | }
56 | renderValue={(selected) => {
57 | if (selected.length === 0) {
58 | return {Props.placeholder}
59 | }
60 |
61 | return selected.join(', ')
62 | }}
63 | MenuProps={MenuProps}
64 | inputProps={{ 'aria-label': t('common.select') }}
65 | >
66 |
69 | {Props.items.map((item) => (
70 |
77 | ))}
78 |
79 |
80 |
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react'
2 | import { LinkProps, useHistory, useLocation } from 'react-router-dom'
3 | import { Button, ButtonProps } from '@mui/material'
4 | import { Theme } from '@mui/material/styles'
5 | import { SxProps } from '@mui/system'
6 |
7 | export const NmLink: React.FC<
8 | Exclude & ButtonProps & { sx?: SxProps }
9 | > = ({ sx, to, children, ...rest }) => {
10 | const history = useHistory()
11 | const location = useLocation()
12 | const navigate = useCallback(() => {
13 | history.push(typeof to === 'function' ? to(location) : to)
14 | }, [to, location, history])
15 |
16 | return (
17 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from '@mui/material'
2 | import Typography from '@mui/material/Typography'
3 | import { LinearProgress } from '@mui/material'
4 | import { useTranslation } from 'react-i18next'
5 |
6 | export default function Loading() {
7 | const { t } = useTranslation()
8 |
9 | return (
10 |
11 |
12 |
19 | {`${t('common.loading')}`}
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/LoadingText.tsx:
--------------------------------------------------------------------------------
1 | import './loading-text.css'
2 |
3 | interface LoadingTextProps {
4 | text: string
5 | }
6 |
7 | export default function LoadingText(props: LoadingTextProps) {
8 | if (props.text) {
9 | return <>{props.text}>
10 | }
11 | return (
12 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/filter/Tablefilter.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowDownward, ArrowUpward } from '@mui/icons-material'
2 | import {
3 | FormControl,
4 | InputLabel,
5 | Select,
6 | MenuItem,
7 | Grid,
8 | IconButton,
9 | Tooltip,
10 | } from '@mui/material'
11 | import { SelectChangeEvent } from '@mui/material/Select'
12 | import { useTranslation } from 'react-i18next'
13 |
14 | export interface TablefilterProps {
15 | values: string[]
16 | onSelect: (selection: string) => void
17 | onAscendClick: () => void
18 | ascending: boolean
19 | currentValue: string
20 | }
21 |
22 | export function Tablefilter(props: TablefilterProps) {
23 | const { values, ascending, onSelect, onAscendClick, currentValue } = props
24 | const { t } = useTranslation()
25 |
26 | return (
27 |
28 |
29 |
30 | {t('common.sortby')}
31 |
41 |
42 |
43 |
44 |
51 |
52 | {ascending ? (
53 |
54 | ) : (
55 |
56 | )}
57 |
58 |
59 |
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/form/FormCheckboxInput.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import CheckboxField from '@mui/material/Checkbox'
3 | import { Controller } from 'react-hook-form'
4 | import FormControlLabel from '@mui/material/FormControlLabel'
5 | import { useFormControl } from './internal/formContext'
6 |
7 | export const NmFormInputCheckbox: FC<{
8 | name: string
9 | label: string
10 | disabled?: boolean
11 | }> = ({ name, label, disabled }) => {
12 | const { control, disabled: formDisabled } = useFormControl()
13 | return (
14 | (
18 |
27 | }
28 | label={label}
29 | />
30 | )}
31 | />
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/form/FormList.tsx:
--------------------------------------------------------------------------------
1 | import { useFieldArray } from 'react-hook-form'
2 | import { useFormControl } from './internal/formContext'
3 | import { FormControlLabelProps } from '@mui/material'
4 | import { Modify } from 'src/types/react-app-env'
5 |
6 | export function NmFormList({
7 | name,
8 | children,
9 | disabled,
10 | }: {
11 | name: string
12 | disabled?: boolean
13 | labelPlacement?: FormControlLabelProps['labelPlacement']
14 | children: (
15 | fields: Array<
16 | Modify string }>
17 | >,
18 | disabled: boolean
19 | ) => JSX.Element
20 | }) {
21 | const { control, disabled: formDisabled } = useFormControl()
22 | const { fields } = useFieldArray({
23 | control,
24 | name,
25 | })
26 |
27 | return children(
28 | fields.map(
29 | (field, index) =>
30 | ({
31 | ...field,
32 | getFieldName: (path: string) => `${name}.${index}.${path}`,
33 | } as any)
34 | ),
35 | formDisabled || !!disabled
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/form/FormOptionSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { TextFieldProps } from '@mui/material/TextField/TextField'
3 | import { Controller } from 'react-hook-form'
4 | import { useFormControl } from './internal/formContext'
5 | import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'
6 |
7 | export const NmFormOptionSelect: React.FC<
8 | Omit<
9 | TextFieldProps,
10 | 'name' | 'onChange' | 'value' | 'error' | 'helperText' | 'rightAlign'
11 | > & {
12 | name: string
13 | rightAlign?: boolean
14 | selections: { key: string; option: any }[]
15 | }
16 | > = ({ name, disabled, rightAlign, selections, ...textfieldProps }) => {
17 | const { control, disabled: formDisabled } = useFormControl()
18 |
19 | return (
20 | (
24 |
25 | {textfieldProps.label}
26 |
41 |
42 | )}
43 | />
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/form/FormSwitchInput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import SwitchField from '@mui/material/Switch'
3 | import { Controller } from 'react-hook-form'
4 | import FormControlLabel from '@mui/material/FormControlLabel'
5 | import { useFormControl } from './internal/formContext'
6 | import { FormControlLabelProps } from '@mui/material'
7 |
8 | export const NmFormInputSwitch: React.FC<{
9 | name: string
10 | label: string
11 | disabled?: boolean
12 | labelPlacement?: FormControlLabelProps['labelPlacement']
13 | defaultValue?: boolean
14 | }> = ({ name, label, disabled, labelPlacement, defaultValue }) => {
15 | const { control, disabled: formDisabled } = useFormControl()
16 | return (
17 | (
21 |
31 | }
32 | label={label}
33 | />
34 | )}
35 | />
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/form/FormTextInput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import TextField, { TextFieldProps } from '@mui/material/TextField/TextField'
3 | import { Controller } from 'react-hook-form'
4 | import { FormContext } from './internal/formContext'
5 |
6 | export const NmFormInputText: React.FC<
7 | Omit<
8 | TextFieldProps,
9 | 'name' | 'onChange' | 'value' | 'error' | 'helperText' | 'rightAlign'
10 | > & {
11 | name: string
12 | rightAlign?: boolean
13 | }
14 | > = ({ name, disabled, rightAlign, ...textfieldProps }) => {
15 |
16 | return (
17 |
18 | {({ control, disabled: formDisabled }) => (
19 | (
23 |
41 | )}
42 | />
43 | )}
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/form/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Form'
2 | export * from './FormCheckboxInput'
3 | export * from './FormSwitchInput'
4 | export * from './FormTextInput'
5 | export * from './validate'
6 | export * from './FormList'
7 |
--------------------------------------------------------------------------------
/src/components/form/internal/formContext.ts:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { Control } from 'react-hook-form'
3 |
4 | export const FormContext = React.createContext<{
5 | control: Control
6 | disabled: boolean
7 | }>({ disabled: false, control: {} as any })
8 |
9 | export const useFormControl = () => useContext(FormContext)
10 |
--------------------------------------------------------------------------------
/src/components/form/validate.ts:
--------------------------------------------------------------------------------
1 | import {
2 | UnpackNestedValue,
3 | FieldError,
4 | ResolverResult,
5 | FieldErrors,
6 | DeepMap,
7 | UnionLike,
8 | DeepPartial,
9 | Resolver,
10 | } from 'react-hook-form'
11 |
12 | export function validate(validators: {
13 | [K in keyof UnpackNestedValue]?: (
14 | value: UnpackNestedValue[K],
15 | formData: UnpackNestedValue
16 | ) =>
17 | | DeepMap>, FieldError>[keyof DeepMap<
18 | DeepPartial>,
19 | FieldError
20 | >]
21 | | undefined
22 | }): Resolver {
23 | return (data: UnpackNestedValue): ResolverResult => {
24 | const keys = Object.keys(data) as Array>
25 | let hasError = false
26 | const errors = {} as FieldErrors //{ [K in keyof T]?: ({ message: string, type: string } | undefined)};
27 | for (let i = 0; i < keys.length; i++) {
28 | const key = keys[i]
29 | const validator = validators[key]
30 | if (validator) {
31 | const error = validator(data[key], data)
32 | if (error) {
33 | hasError = true
34 | errors[key as keyof FieldErrors] = error
35 | }
36 | }
37 | }
38 |
39 | if (hasError) {
40 | return {
41 | errors,
42 | values: {},
43 | }
44 | }
45 | return {
46 | errors: {},
47 | values: data,
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { NmLink } from './Link'
2 | export { NmCheckbox } from './Checkbox'
3 | export { darkTheme, lightTheme, useCurrentTheme } from './theme'
4 |
--------------------------------------------------------------------------------
/src/components/loading-text.css:
--------------------------------------------------------------------------------
1 | @keyframes pulsate-fwd {
2 | 0% {
3 | width: 2px;
4 | height: 2px;
5 | }
6 | 50% {
7 | width: 4px;
8 | height: 4px;
9 | }
10 | 100% {
11 | width: 2px;
12 | height: 2px;
13 | }
14 | }
15 |
16 | .loading-text {
17 | display: flex;
18 | align-items: center;
19 | height: 2rem;
20 | }
21 |
22 | .loading-text .dot-1,
23 | .loading-text .dot-2,
24 | .loading-text .dot-3 {
25 | width: 2px;
26 | height: 2px;
27 | background-color: teal;
28 | border-radius: 50%;
29 | margin-right: 4px;
30 | animation: pulsate-fwd 0.7s ease-in-out infinite both;
31 | }
32 |
33 | .loading-text .dot-2 {
34 | animation-delay: 100ms;
35 | }
36 |
37 | .loading-text .dot-3 {
38 | animation-delay: 200ms;
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material/styles'
2 | import { orange } from '@mui/material/colors'
3 | import { ThemeOptions, PaletteMode } from '@mui/material'
4 |
5 | // file for theme choices
6 | const netmakerColors = {
7 | purple: '#73509f',
8 | blue: '#119da4',
9 | green: '#0ead69',
10 | orange: '#fd4c41',
11 | black: '#110b35',
12 | white: '#f5f5f5',
13 | }
14 |
15 | export const darkTheme = createTheme({
16 | status: {
17 | danger: orange[500],
18 | },
19 | palette: {
20 | mode: 'dark' as PaletteMode,
21 | primary: {
22 | main: netmakerColors.blue,
23 | },
24 | secondary: {
25 | main: netmakerColors.orange,
26 | },
27 | common: {
28 | black: netmakerColors.black,
29 | white: netmakerColors.white,
30 | },
31 | // background: {
32 | // paper: netmakerColors.black,
33 | // },
34 | },
35 | } as ThemeOptions)
36 |
37 | export const lightTheme = createTheme({
38 | palette: {
39 | mode: 'light' as PaletteMode,
40 | primary: {
41 | main: netmakerColors.blue,
42 | },
43 | secondary: {
44 | main: netmakerColors.orange,
45 | },
46 | common: {
47 | black: netmakerColors.black,
48 | white: netmakerColors.white,
49 | },
50 | },
51 | status: {
52 | danger: netmakerColors.orange,
53 | },
54 | } as ThemeOptions)
55 |
56 | // let currentTheme = darkTheme
57 |
58 | export const useCurrentTheme = (mode: 'light' | 'dark') => {
59 | if (mode === 'light') {
60 | return lightTheme
61 | }
62 | return darkTheme
63 | }
64 |
65 | // export const useSetTheme = () => (theme: Theme) => (currentTheme = theme)
66 |
--------------------------------------------------------------------------------
/src/components/utils.tsx:
--------------------------------------------------------------------------------
1 |
2 | export const MAX_ATTEMPTS = 10
3 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import { version } from '../package.json'
2 | // window.REACT_APP_BACKEND for container usage
3 | // if running locally or statically, change the 'http://localhost:8081' to desired backend URL and then build
4 | export const BACKEND_URL =
5 | (window as any).REACT_APP_BACKEND || 'http://localhost:8081'
6 |
7 | export const DEBUG = BACKEND_URL.includes('localhost')
8 |
9 | const extractVersion = (v: string | undefined) => {
10 | if (!!!v) {
11 | return 'latest'
12 | }
13 | if (v.charAt(0) === 'v' || v.charAt(0) === 'V') {
14 | return v
15 | }
16 | return `v${v}`
17 | }
18 |
19 | // == set UI version here ==
20 | export const UI_VERSION = extractVersion(version)
21 |
22 | // == local storage keys ==
23 | export const USER_KEY = 'netmaker-user'
24 | export const SETTINGS_KEY = 'netmaker-settings'
25 |
--------------------------------------------------------------------------------
/src/ee/LICENSE:
--------------------------------------------------------------------------------
1 | The Netmaker Enterprise license (the “Enterprise License”)
2 | Copyright (c) 2022 Netmaker, Inc.
3 |
4 | With regard to the Netmaker Software:
5 |
6 | This software and associated documentation files (the "Software") may only be used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Netmaker Subscription Terms of Service, available at https://netmaker.io/terms (the “Enterprise Terms”), or other agreement governing the use of the Software, as agreed by you and Netmaker, and otherwise have a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients. Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients as allocated by the license. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes, without requiring a subscription. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
10 | For all third party components incorporated into the Netmaker Software, those components are licensed under the original license provided by the owner of the applicable component.
11 |
--------------------------------------------------------------------------------
/src/ee/metrics/MetricRoute.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { NetworkSelect } from '~components/NetworkSelect'
7 | import { MetricsTable } from './views/MetricsTable'
8 | import { NodeMetrics } from './views/NodeMetrics'
9 | import { ExtMetrics } from './views/ExtMetrics'
10 |
11 | export const MetricRoute: React.FC = () => {
12 | const { path } = useRouteMatch()
13 | const { t } = useTranslation()
14 |
15 | useLinkBreadcrumb({
16 | title: t('pro.metrics'),
17 | })
18 |
19 | const titleStyle = {
20 | textAlign: 'center',
21 | } as any
22 |
23 | return (
24 |
25 |
26 |
27 |
33 |
34 |
35 | {t('pro.metrics')}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/src/ee/metrics/components/MetricButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@mui/material'
2 | import React from 'react'
3 | import { useHistory } from 'react-router-dom'
4 | import { useDispatch } from 'react-redux'
5 | import { clearCurrentMetrics } from '~store/modules/server/actions'
6 |
7 | type Props = {
8 | link: string
9 | text: string | undefined
10 | sx?: any
11 | logic?: () => void
12 | }
13 |
14 | export default function MetricButton(props: Props) {
15 | const dispatch = useDispatch()
16 | const history = useHistory()
17 |
18 | return ()
31 | }
32 |
--------------------------------------------------------------------------------
/src/ee/metrics/util.tsx:
--------------------------------------------------------------------------------
1 | // Utils for metrics
2 | export const getTimeMinHrs = (duration: number) => {
3 | const minutes = duration / 60000000000
4 | const hours = Math.floor(minutes / 60)
5 | const remainingMinutes = Math.ceil(((minutes / 60) % 1) * 60)
6 | return {hours, min: remainingMinutes}
7 | }
8 |
--------------------------------------------------------------------------------
/src/ee/metrics/views/charts/ReceivedChart.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Chart as ChartJS,
4 | CategoryScale,
5 | LinearScale,
6 | BarElement,
7 | Title,
8 | Tooltip,
9 | Legend,
10 | } from 'chart.js';
11 | import { Bar } from 'react-chartjs-2';
12 | import { getTimeMinHrs } from '../../util';
13 |
14 | ChartJS.register(
15 | CategoryScale,
16 | LinearScale,
17 | BarElement,
18 | Title,
19 | Tooltip,
20 | Legend
21 | );
22 |
23 | export const options = {
24 | responsive: true,
25 | plugins: {
26 | legend: {
27 | position: 'top' as const,
28 | },
29 | title: {
30 | display: true,
31 | text: 'Total Transmitted Data on Node',
32 | },
33 | },
34 | };
35 |
36 | const labels = ['Received'];
37 |
38 | export const data = {
39 | labels,
40 | datasets: [
41 | {
42 | label: 'Received',
43 | data: [100],
44 | backgroundColor: 'rgba(255, 99, 132, 0.5)',
45 | },
46 | {
47 | label: 'Received per Hour',
48 | data: [Math.floor(Math.random() * 1000)],
49 | backgroundColor: 'rgba(53, 162, 235, 0.5)',
50 | },
51 | ],
52 | };
53 |
54 | export const getData = (totalReceived: number, duration: number) => {
55 | const { hours, min } = getTimeMinHrs(duration)
56 | const totalTimeHours = hours + (min/60)
57 | const receivedPerHour = !!!duration ? 0 : totalReceived / totalTimeHours
58 | return ({
59 | labels,
60 | datasets: [
61 | {
62 | label: 'Received',
63 | data: [totalReceived],
64 | backgroundColor: 'rgba(255, 99, 132, 0.5)',
65 | },
66 | {
67 | label: 'Received per Hour',
68 | data: [receivedPerHour],
69 | backgroundColor: 'rgba(53, 162, 235, 0.5)',
70 | },
71 | ],
72 | })}
73 |
74 | type ReceivedProps = {
75 | totalReceived: number
76 | duration: number
77 | }
78 |
79 | export function ReceivedChart({ totalReceived, duration }: ReceivedProps) {
80 | return ;
81 | }
82 |
--------------------------------------------------------------------------------
/src/ee/metrics/views/charts/SentChart.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Chart as ChartJS,
4 | CategoryScale,
5 | LinearScale,
6 | BarElement,
7 | Title,
8 | Tooltip,
9 | Legend,
10 | } from 'chart.js';
11 | import { Bar } from 'react-chartjs-2';
12 | import { getTimeMinHrs } from '../../util'
13 |
14 | ChartJS.register(
15 | CategoryScale,
16 | LinearScale,
17 | BarElement,
18 | Title,
19 | Tooltip,
20 | Legend
21 | );
22 |
23 | export const options = {
24 | responsive: true,
25 | plugins: {
26 | legend: {
27 | position: 'top' as const,
28 | },
29 | title: {
30 | display: true,
31 | text: 'Total Transmitted Data on Node',
32 | },
33 | },
34 | };
35 |
36 | const labels = ['Sent'];
37 |
38 | export const getData = (totalSent: number, duration: number) => {
39 | const { hours, min } = getTimeMinHrs(duration)
40 | const totalTimeHours = hours + (min/60)
41 | const sentPerHour = !!!duration ? 0 : totalSent / totalTimeHours
42 | return ({
43 | labels,
44 | datasets: [
45 | {
46 | label: 'Sent',
47 | data: [totalSent],
48 | backgroundColor: 'rgba(255, 99, 132, 0.5)',
49 | },
50 | {
51 | label: 'Sent per Hour',
52 | data: [sentPerHour],
53 | backgroundColor: 'rgba(53, 162, 235, 0.5)',
54 | },
55 | ],
56 | })}
57 |
58 | type SentProps = {
59 | totalSent: number
60 | duration: number
61 | }
62 |
63 | export function SentChart({totalSent, duration}: SentProps) {
64 | return ;
65 | }
66 |
--------------------------------------------------------------------------------
/src/ee/metrics/views/charts/Uptime.tsx:
--------------------------------------------------------------------------------
1 | import { Typography } from "@mui/material";
2 | import { Chart, registerables } from "chart.js";
3 | import React from "react";
4 | import { Doughnut } from 'react-chartjs-2'
5 | import { getTimeMinHrs } from "../../util";
6 |
7 | Chart.register(...registerables);
8 |
9 | interface Props {
10 | chartData: number[];
11 | actualUptime?: number
12 | }
13 |
14 | const styles = {
15 | doughContainer: {
16 | width: "40%",
17 | height: "40%",
18 | top: "50%",
19 | left: "50%",
20 | position: "absolute",
21 | transform: "translate(-50%, -25%)"
22 | },
23 | relative: {
24 | position: "relative"
25 | }
26 | } as any
27 |
28 | const options = {
29 | plugins: {
30 | legend: {
31 | display: false
32 | },
33 | },
34 | cutout: 55,
35 | elements: {
36 | arc: {
37 | borderWidth: 0,
38 | }
39 | }
40 | };
41 |
42 | export const UptimeChart = (props: Props) => {
43 | // helper function to format chart data since you do this twice
44 | const formatData = (data: number[]) => ({
45 | responsive: true,
46 | labels: ["Up", "Down"],
47 | datasets: [{
48 | label: 'Uptime',
49 | data: data,
50 | backgroundColor: [
51 | 'rgb(54, 255, 120)',
52 | 'rgb(255, 99, 132)',
53 | ],
54 | hoverOffset: 4
55 | }]
56 | });
57 |
58 | let timeString = ""
59 | if (!!props.actualUptime) {
60 | const {hours, min} = getTimeMinHrs(props.actualUptime)
61 | timeString = `${hours}h${min}m`
62 | } else {
63 | timeString= '0h0m'
64 | }
65 |
66 | return (
67 |
68 |
69 |
70 | {!!timeString &&
71 |
72 | {timeString}
73 |
74 |
}
75 |
76 |
77 | );
78 | };
79 |
--------------------------------------------------------------------------------
/src/ee/nodes/FailoverButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import { useTranslation } from 'react-i18next'
4 | import { IconButton, Tooltip } from '@mui/material'
5 | import { Block, Check, AirlineStops } from '@mui/icons-material'
6 | import {
7 | createIngressNode, deleteIngressNode,
8 | } from '~modules/node/actions'
9 | import { Node } from '~store/types'
10 | import CustomDialog from '~components/dialog/CustomDialog'
11 |
12 | const hoverRedStyle = {
13 | ':hover': {
14 | color: 'red',
15 | },
16 | }
17 |
18 | const hoverBlueStyle = {
19 | ':hover': {
20 | color: '#3f51b5',
21 | },
22 | }
23 |
24 | export const FailoverButton: React.FC<{
25 | node: Node
26 | }> = ({
27 | node,
28 | }) => {
29 | const dispatch = useDispatch()
30 | const [hovering, setHovering] = React.useState(false)
31 | const [open, setOpen] = React.useState(false)
32 | const { t } = useTranslation()
33 |
34 | const handleHoverEnter = () => setHovering(true)
35 | const handleHoverLeave = () => setHovering(false)
36 |
37 | const handleOpen = () => setOpen(true)
38 | const handleClose = () => setOpen(false)
39 |
40 | const createIngress = () => {
41 | dispatch(
42 | createIngressNode.request({
43 | netid: node.network,
44 | nodeid: node.id,
45 | failover: true,
46 | })
47 | )
48 | }
49 |
50 | const removeAction = () => {
51 | dispatch(
52 | deleteIngressNode.request({
53 | netid: node.network,
54 | nodeid: node.id,
55 | })
56 | )
57 | }
58 |
59 | return (
60 | <>
61 | (!node.failover ? createIngress() : removeAction())}
65 | message={`${t('pro.helpers.failover')}`}
66 | title={`${!node.failover ? t('common.create') : t('common.delete')} ${t('node.failover')}`}
67 | />
68 |
69 |
76 | {!node.failover ? : hovering ? : }
77 |
78 |
79 | >
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/src/i18n/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18n, { Resource } from 'i18next'
2 | import { initReactI18next } from 'react-i18next'
3 | import { produce } from 'immer'
4 |
5 | import { en } from './resources/en'
6 |
7 | // the translations
8 | // (tip move them in a JSON file and import them,
9 | // or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files)
10 | const resources: Resource = {
11 | en: {
12 | translation: en,
13 | },
14 | }
15 |
16 | // Used to check for missing keys in dev consolse
17 | ;(window as any).missingTranslations = {}
18 |
19 | i18n
20 | .use(initReactI18next) // passes i18n down to react-i18next
21 | .init({
22 | resources,
23 | lng: 'en', // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
24 | // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
25 | // if you're using a language detector, do not define the lng option
26 | saveMissing: true,
27 | missingKeyHandler: (lngs, ns, key, fallbackValue) => {
28 | ;(window as any).missingTranslations = produce<{}, any>(
29 | (window as any).missingTranslations,
30 | (draftState) => {
31 | draftState[`${key}`] = fallbackValue
32 | }
33 | )
34 | },
35 | interpolation: {
36 | escapeValue: false, // react already safes from xss
37 | },
38 | appendNamespaceToMissingKey: true,
39 | })
40 |
41 | export { i18n }
42 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from './App'
5 | import * as serviceWorker from './serviceWorker'
6 | import { createReduxStore } from './store/createStore'
7 | import { Provider } from 'react-redux'
8 | import { BrowserRouter } from 'react-router-dom'
9 | import './i18n/i18n'
10 |
11 | const store = createReduxStore()
12 |
13 | ReactDOM.render(
14 |
15 |
16 |
17 |
18 |
19 |
20 | ,
21 | document.getElementById('root')
22 | )
23 |
24 | // If you want your app to work offline and load faster, you can change
25 | // unregister() to register() below. Note this comes with some pitfalls.
26 | // Learn more about service workers: https://bit.ly/CRA-PWA
27 | serviceWorker.unregister()
28 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/netmaker-logo-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gravitl/netmaker-ui/9083e5e0f8202df166e4b664dded6b401d4fc74f/src/netmaker-logo-2.png
--------------------------------------------------------------------------------
/src/netmaker-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gravitl/netmaker-ui/9083e5e0f8202df166e4b664dded6b401d4fc74f/src/netmaker-logo.png
--------------------------------------------------------------------------------
/src/proroute/proaccesskeys/ProDashboardAccessKeys.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { ProAccessKeyCreate } from './components/ProAccessKeyCreate'
7 | import { ProAccessKeyTable } from './components/ProAccessKeyTable'
8 | import { ProAccessKeyView } from './components/ProAccessKeyView'
9 |
10 | export const ProDashboardAccessKeys: React.FC = () => {
11 | const { path } = useRouteMatch()
12 | const { t } = useTranslation()
13 |
14 | useLinkBreadcrumb({
15 | title: t('breadcrumbs.accessKeys'),
16 | })
17 |
18 | return (
19 |
20 |
21 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/src/proroute/proaccesskeys/components/ProAccessKeyView.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { proSelectors } from '~store/types'
4 | import { useHistory, useParams } from 'react-router'
5 | import AccessKeyDetails from '../../../route/accesskeys/components/AccessKeyDetails'
6 | import { useTranslation } from 'react-i18next'
7 | import { NotFound } from '~util/errorpage'
8 |
9 | export const ProAccessKeyView: React.FC<{}> = () => {
10 | const history = useHistory()
11 | const { t } = useTranslation()
12 | const { netid, keyname } = useParams<{ netid: string; keyname: string }>()
13 | const userData = useSelector(proSelectors.networkUserData)[netid]
14 | if (!!!userData) {
15 | return
16 | }
17 | const networks = userData.networks
18 | const netIndex = networks.findIndex((network) => network.netid === netid)
19 | const accessKeys = networks[netIndex].accesskeys
20 | const keyIndex = accessKeys.findIndex(
21 | (accessKey) => accessKey.name === keyname
22 | )
23 | const accessKeyInfo = accessKeys[keyIndex]
24 |
25 | const handleClose = () => history.goBack()
26 |
27 | return (
28 | {}}
32 | handleClose={handleClose}
33 | accessString={accessKeyInfo.accessstring}
34 | keyValue={accessKeyInfo.value}
35 | />
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/proroute/proaccessleveldashboards/NetUserSwitch.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { useParams } from 'react-router-dom'
4 | import { proSelectors } from '~store/selectors'
5 | import { NetAdminDashboard } from '../pronetadmin/NetAdminDashboard'
6 | import { NodeUserDashboard } from '../pronodeuser/NodeUserDashboard'
7 | import {
8 | NET_ADMIN_ACCESS_LVL,
9 | NODE_ACCESS_LVL,
10 | CLIENT_ACCESS_LVL,
11 | } from '../proutils/ProConsts'
12 |
13 | import { NotFound } from '~util/errorpage'
14 | import { VpnDashboard } from '../prouser/VpnDashboard'
15 |
16 | export default function NetUserView() {
17 | const { netid } = useParams<{ netid: string }>()
18 | const allUserData = useSelector(proSelectors.networkUserData)
19 |
20 | if (!!!allUserData || !!!allUserData[netid]) {
21 | return
22 | }
23 |
24 | const userData = allUserData[netid]
25 |
26 | switch (userData.user.accesslevel) {
27 | case NET_ADMIN_ACCESS_LVL:
28 | return
29 | case NODE_ACCESS_LVL:
30 | return
31 | case CLIENT_ACCESS_LVL:
32 | return
33 | default:
34 | return NO ACCESS
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/proroute/proaccessleveldashboards/components/AdminNetwork.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Link, useParams } from 'react-router-dom'
3 | import Avatar from '@mui/material/Avatar'
4 | import Card from '@mui/material/Card'
5 |
6 | import { grey } from '@mui/material/colors'
7 |
8 | import CardContent from '@mui/material/CardContent'
9 | import Typography from '@mui/material/Typography'
10 | import { useTranslation } from 'react-i18next'
11 | import { Wifi } from '@mui/icons-material'
12 | import { Button, useTheme } from '@mui/material'
13 |
14 | export default function AdminNetworkCard() {
15 | const { t } = useTranslation()
16 | const theme = useTheme()
17 | const { netid } = useParams<{ netid: string }>()
18 |
19 | const cardStyle = {
20 | marginBottom: '1em',
21 | marginTop: '1em',
22 | height: '100%',
23 | minHeight: '14em',
24 | width: '100%',
25 | padding: '.5em',
26 | }
27 |
28 | const cardContentStyle = {
29 | display: 'flex',
30 | justifyContent: 'center',
31 | alignItems: 'center',
32 | flexDirection: 'column',
33 | } as any
34 |
35 | return (
36 |
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/src/proroute/proaccessleveldashboards/components/ExtAccessCard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Link, useParams } from 'react-router-dom'
3 | import Card from '@mui/material/Card'
4 |
5 | // import CreateIcon from '@mui/icons-material/AddBox';
6 | import { grey } from '@mui/material/colors'
7 | import Avatar from '@mui/material/Avatar'
8 | import { ExternalClient } from '~store/types'
9 |
10 | import CardContent from '@mui/material/CardContent'
11 | import Typography from '@mui/material/Typography'
12 | import { useTranslation } from 'react-i18next'
13 | import { Devices } from '@mui/icons-material'
14 | import { Button, useTheme } from '@mui/material'
15 |
16 | export default function ExtAccessCard(Props: { clients: ExternalClient[] }) {
17 | const { t } = useTranslation()
18 | const theme = useTheme()
19 | const { netid } = useParams<{ netid: string }>()
20 |
21 | const cardStyle = {
22 | marginBottom: '1em',
23 | marginTop: '1em',
24 | height: '100%',
25 | width: '100%',
26 | minHeight: '14em',
27 | }
28 |
29 | const cardContentStyle = {
30 | display: 'flex',
31 | justifyContent: 'center',
32 | alignItems: 'center',
33 | flexDirection: 'column',
34 | } as any
35 |
36 | return (
37 |
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/src/proroute/proaccessleveldashboards/components/NodeAccessCard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Link, useParams } from 'react-router-dom'
3 | import Card from '@mui/material/Card'
4 | import Avatar from '@mui/material/Avatar'
5 |
6 | import { grey } from '@mui/material/colors'
7 | import CardContent from '@mui/material/CardContent'
8 | import Typography from '@mui/material/Typography'
9 | import { useTranslation } from 'react-i18next'
10 | import { DeviceHub } from '@mui/icons-material'
11 | import { Button, useTheme } from '@mui/material'
12 |
13 | export default function NodeAccessCard() {
14 | const { t } = useTranslation()
15 | const theme = useTheme()
16 | const { netid } = useParams<{ netid: string }>()
17 |
18 | const cardStyle = {
19 | marginBottom: '1em',
20 | marginTop: '1em',
21 | height: '100%',
22 | width: '100%',
23 | minHeight: '14em',
24 | }
25 |
26 | const cardContentStyle = {
27 | display: 'flex',
28 | justifyContent: 'center',
29 | alignItems: 'center',
30 | flexDirection: 'column',
31 | } as any
32 |
33 | return (
34 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/proroute/proaccessleveldashboards/components/ProAccessKeyCard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Link, useParams } from 'react-router-dom'
3 | import Card from '@mui/material/Card'
4 |
5 | // import CreateIcon from '@mui/icons-material/AddBox';
6 | import { grey } from '@mui/material/colors'
7 | import Avatar from '@mui/material/Avatar'
8 | import CardContent from '@mui/material/CardContent'
9 | import Typography from '@mui/material/Typography'
10 | import { useTranslation } from 'react-i18next'
11 | import { VpnKey } from '@mui/icons-material'
12 | import { Button, useTheme } from '@mui/material'
13 |
14 | export default function ProAccessKeyCard() {
15 | const { t } = useTranslation()
16 | const theme = useTheme()
17 | const { netid } = useParams<{ netid: string }>()
18 |
19 | const cardStyle = {
20 | marginBottom: '1em',
21 | marginTop: '1em',
22 | width: '100%',
23 | height: '100%',
24 | minHeight: '14em',
25 | }
26 |
27 | const cardContentStyle = {
28 | display: 'flex',
29 | justifyContent: 'center',
30 | alignItems: 'center',
31 | flexDirection: 'column',
32 | } as any
33 |
34 | return (
35 |
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/src/proroute/proaccessleveldashboards/components/WelcomeCard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import Avatar from '@mui/material/Avatar'
4 | import Card from '@mui/material/Card'
5 | import { grey } from '@mui/material/colors'
6 | import { useSelector } from 'react-redux'
7 | import CardContent from '@mui/material/CardContent'
8 | import Typography from '@mui/material/Typography'
9 | import { useTranslation } from 'react-i18next'
10 | import ThreePIcon from '@mui/icons-material/ThreeP'
11 | import { Button, useTheme } from '@mui/material'
12 | import { authSelectors } from '~store/selectors'
13 |
14 | export default function WelcomeCard() {
15 | const { t } = useTranslation()
16 | const theme = useTheme()
17 | const user = useSelector(authSelectors.getUser)
18 |
19 | const cardStyle = {
20 | marginBottom: '1em',
21 | marginTop: '1em',
22 | height: '100%',
23 | width: '50%',
24 | MaxWidth: '50%',
25 | minHeight: '15em',
26 | }
27 |
28 | const cardContentStyle = {
29 | display: 'flex',
30 | justifyContent: 'center',
31 | alignItems: 'center',
32 | flexDirection: 'column',
33 | } as any
34 |
35 | return (
36 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/src/proroute/pronetadmin/components/ProNetworkModifiedStats.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, TextField } from '@mui/material'
2 | import { useTranslation } from 'react-i18next'
3 | import { datePickerConverter } from '~util/unixTime'
4 | import { useNetwork } from '~util/network'
5 |
6 | export const ProNetworkModifiedStats: React.FC<{ netid: string }> = ({
7 | netid,
8 | }) => {
9 | const network = useNetwork(netid)
10 | const { t } = useTranslation()
11 |
12 | const fieldStyle = {
13 | marginTop: '1em',
14 | }
15 |
16 | if (!network) {
17 | return null
18 | }
19 | return (
20 |
27 |
28 |
38 |
39 |
40 |
41 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/src/proroute/pronodeuser/NodeUserDashboard.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route, useParams } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import NodeAccessCard from '../proaccessleveldashboards/components/NodeAccessCard'
7 | import ExtAccessCard from '../proaccessleveldashboards/components/ExtAccessCard'
8 | import { useSelector } from 'react-redux'
9 | import { proSelectors } from '~store/selectors'
10 | import { ExternalClient } from '~store/types'
11 | import { RemoteAccessView } from './views/RemoteAccessView'
12 | import { ProNodesView } from './components/ProNodesView'
13 |
14 | export const NodeUserDashboard: React.FC = () => {
15 | const { path } = useRouteMatch()
16 | const { t } = useTranslation()
17 | const { netid } = useParams<{ netid: string }>()
18 | const netData = useSelector(proSelectors.networkUserData)[netid]
19 | let clients = [] as ExternalClient[]
20 |
21 | if (!!netData) {
22 | clients = netData.clients
23 | }
24 |
25 | useLinkBreadcrumb({
26 | title: t('breadcrumbs.nodeuserdashboard'),
27 | })
28 |
29 | return (
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/src/proroute/pronodeuser/components/ProNodesView.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route, useParams } from 'react-router-dom'
4 | import { useSelector } from 'react-redux'
5 | import { proSelectors } from '~store/selectors'
6 | import { Node } from '~store/types'
7 | import { NodeAccessView } from './NodeAccessView'
8 | import { NET_ADMIN_ACCESS_LVL } from '../../proutils/ProConsts'
9 | import { ProNodeId } from './ProNodeId'
10 | import { NotFound } from '~util/errorpage'
11 | import { ProNodeEdit } from './ProNodeEdit'
12 |
13 | export const ProNodesView: React.FC = () => {
14 | const { path } = useRouteMatch()
15 | const { netid } = useParams<{ netid: string }>()
16 | const netData = useSelector(proSelectors.networkUserData)[netid]
17 | let nodes = [] as Node[]
18 |
19 | if (!!netData) {
20 | nodes = netData.nodes
21 | } else {
22 | return
23 | }
24 |
25 | return (
26 |
27 |
28 |
29 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {netData.user.accesslevel === NET_ADMIN_ACCESS_LVL && (
52 |
53 |
54 |
55 |
56 |
57 | )}
58 |
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/src/proroute/pronodeuser/views/RemoteAccessView.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route, useParams } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { ExtClientViewVpn } from '../../../proroute/prouser/components/ExtClientViewVpn'
7 | import { useSelector } from 'react-redux'
8 | import { proSelectors } from '~store/selectors'
9 | import { ExternalClient, Node } from '~store/types'
10 | import { ExtClientEditVpn } from '../../../proroute/prouser/components/ExtClientEditVpn'
11 | import { QrCodeViewVpn } from '../../../proroute/prouser/components/QrCodeViewVpn'
12 |
13 | export const RemoteAccessView: React.FC = () => {
14 | const { path } = useRouteMatch()
15 | const { t } = useTranslation()
16 | const { netid } = useParams<{ netid: string }>()
17 | const netData = useSelector(proSelectors.networkUserData)[netid]
18 | let clients = [] as ExternalClient[]
19 | let vpns = [] as Node[]
20 |
21 | if (!!netData) {
22 | clients = netData.clients
23 | vpns = netData.vpns
24 | }
25 |
26 | useLinkBreadcrumb({
27 | title: t('breadcrumbs.netadmindashboard'),
28 | })
29 |
30 | return (
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/src/proroute/prouser/ProUserView.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { ProNetworkSelect } from '../proutils/ProNetworkSelect'
7 | import { useSelector } from 'react-redux'
8 | import { proSelectors } from '~store/types'
9 | import Loading from '~components/Loading'
10 | import NetUserView from '../proaccessleveldashboards/NetUserSwitch'
11 |
12 | export const ProUserView: React.FC = () => {
13 | const { path } = useRouteMatch()
14 | const { t } = useTranslation()
15 | const isLoading = useSelector(proSelectors.isProcessing)
16 |
17 | useLinkBreadcrumb({
18 | title: t('breadcrumbs.userdashboard'),
19 | })
20 |
21 | if (isLoading) return
22 |
23 | return (
24 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/src/proroute/prouser/VpnDashboard.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route, useParams } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import ExtAccessCard from '../proaccessleveldashboards/components/ExtAccessCard'
7 | import { useSelector } from 'react-redux'
8 | import { proSelectors } from '~store/selectors'
9 | import { ExternalClient } from '~store/types'
10 | import { RemoteAccessView } from '../pronodeuser/views/RemoteAccessView'
11 |
12 | export const VpnDashboard: React.FC = () => {
13 | const { path } = useRouteMatch()
14 | const { t } = useTranslation()
15 | const { netid } = useParams<{ netid: string }>()
16 | const netData = useSelector(proSelectors.networkUserData)[netid]
17 | let clients = [] as ExternalClient[]
18 |
19 | if (!!netData) {
20 | clients = netData.clients
21 | }
22 |
23 | useLinkBreadcrumb({
24 | title: t('breadcrumbs.netadmindashboard'),
25 | })
26 |
27 | return (
28 |
29 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/src/proroute/prouser/components/DeleteExtClientButtonVpn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Delete } from '@mui/icons-material'
3 | import { IconButton, Tooltip } from '@mui/material'
4 | import { useDispatch } from 'react-redux'
5 | import { i18n } from '../../../i18n/i18n'
6 | import { deleteExternalClient } from '~store/modules/node/actions'
7 | import { ExternalClient } from '~store/types'
8 | import CustomizedDialogs from '~components/dialog/CustomDialog'
9 |
10 | export const DeleteExtClientButtonVpn: React.FC<{
11 | client: ExternalClient
12 | }> = ({ client }) => {
13 | const dispatch = useDispatch()
14 | const [open, setOpen] = React.useState(false)
15 |
16 | const handleDeleteClient = () => {
17 | dispatch(
18 | deleteExternalClient.request({
19 | clientName: client.clientid,
20 | netid: client.network,
21 | })
22 | )
23 | }
24 |
25 | const handleClose = () => setOpen(false)
26 |
27 | const handleOpen = () => setOpen(true)
28 |
29 | return (
30 | <>
31 |
38 |
42 |
43 |
44 |
45 |
46 | >
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/proroute/prouser/components/DownloadExtClientButtonVpn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Download, QrCode2 } from '@mui/icons-material'
3 | import { IconButton, Tooltip } from '@mui/material'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import { i18n } from '../../../i18n/i18n'
6 | import { getExternalClientConf } from '~store/modules/node/actions'
7 | import { authSelectors, ExternalClient } from '~store/types'
8 | import { useHistory, useRouteMatch } from 'react-router'
9 |
10 | export const DownloadExtClientButtonVpn: React.FC<{
11 | client: ExternalClient
12 | type: 'qr' | 'file'
13 | }> = ({ client, type }) => {
14 | const dispatch = useDispatch()
15 | const token = useSelector(authSelectors.getToken)
16 | const history = useHistory()
17 | const { url } = useRouteMatch()
18 |
19 | const handleDownloadConf = () => {
20 | dispatch(
21 | getExternalClientConf.request({
22 | clientid: client.clientid,
23 | netid: client.network,
24 | type,
25 | token: token || '',
26 | })
27 | )
28 |
29 | if (type === 'qr') {
30 | history.push(`${url}/${client.clientid}/qr`)
31 | }
32 | }
33 |
34 | return (
35 |
43 |
44 | {type === 'file' ? : }
45 |
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/proroute/prouser/components/EditExtClientButtonVpn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Tooltip } from '@mui/material'
3 | import { i18n } from '../../../i18n/i18n'
4 | import { ExternalClient } from '~store/types'
5 | import { useRouteMatch } from 'react-router'
6 | import { NmLink } from '~components/Link'
7 |
8 | export const EditExtClientButtonVpn: React.FC<{
9 | client: ExternalClient
10 | }> = ({ client }) => {
11 | const { url } = useRouteMatch()
12 |
13 | return (
14 |
18 |
23 | {client.clientid}
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/proroute/prouser/components/ExtClientCreateButtonVpn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { AddCircleOutline } from '@mui/icons-material'
3 | import { IconButton, Tooltip } from '@mui/material'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import { useTranslation } from 'react-i18next'
6 | import { createExternalClient } from '~store/modules/node/actions'
7 | import { Node, proSelectors } from '~store/types'
8 | import { useParams } from 'react-router-dom'
9 |
10 | export const ExtClientCreateButtonVpn: React.FC<{
11 | node: Node
12 | }> = ({ node }) => {
13 | const dispatch = useDispatch()
14 | const userData = useSelector(proSelectors.networkUserData)
15 | const { netid } = useParams<{ netid: string }>()
16 | const { t } = useTranslation()
17 |
18 | const data = userData[netid]
19 |
20 | const clientCount = data.clients.length
21 | const clientsLeft = data.user.clientlimit - clientCount
22 | const clientDisable = clientsLeft <= 0
23 |
24 | const handleExtClientCreate = () => {
25 | dispatch(
26 | createExternalClient.request({
27 | netid: node.network,
28 | nodeid: node.id,
29 | })
30 | )
31 | }
32 |
33 | return (
34 |
42 |
47 |
48 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/proroute/prouser/components/NetClientsViewVpn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { useRouteMatch, useHistory } from 'react-router-dom'
4 | import { networkSelectors } from '~store/types'
5 | import { useTranslation } from 'react-i18next'
6 | import CustomSelect from '../../../components/CustomSelect'
7 | import { Grid } from '@mui/material'
8 |
9 | export const NetClientsVpn: React.FC = () => {
10 | const listOfNetworks = useSelector(networkSelectors.getNetworks)
11 | const networkNames = []
12 | if (listOfNetworks) {
13 | for (let i = 0; i < listOfNetworks.length; i++) {
14 | networkNames.push(listOfNetworks[i].netid)
15 | }
16 | }
17 | const { path } = useRouteMatch()
18 | const { t } = useTranslation()
19 | const history = useHistory()
20 |
21 | const centerStyle = {
22 | textAlign: 'center',
23 | } as any
24 |
25 | return (
26 |
27 |
28 | {
31 | history.push(`${path}/${selected}`)
32 | }}
33 | items={networkNames}
34 | />
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/proroute/prouser/components/NetworkSelectVpn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { useRouteMatch, useHistory } from 'react-router-dom'
4 | import { networkSelectors } from '~store/types'
5 | import { useTranslation } from 'react-i18next'
6 | import CustomSelect from '../../../components/CustomSelect'
7 | import { Grid } from '@mui/material'
8 |
9 | export const NetworkSelectVpn: React.FC = () => {
10 | const listOfNetworks = useSelector(networkSelectors.getNetworks)
11 | const networkNames = []
12 | if (listOfNetworks) {
13 | for (let i = 0; i < listOfNetworks.length; i++) {
14 | networkNames.push(listOfNetworks[i].netid)
15 | }
16 | }
17 | const { path } = useRouteMatch()
18 | const { t } = useTranslation()
19 | const history = useHistory()
20 |
21 | const centerStyle = {
22 | textAlign: 'center',
23 | } as any
24 |
25 | return (
26 |
27 |
28 | {
31 | history.push(`${path}/${selected}`)
32 | }}
33 | items={networkNames}
34 | />
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/proroute/prouser/components/QrCodeViewVpn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Grid, Modal, Typography, Box } from '@mui/material'
3 | import { useDispatch, useSelector } from 'react-redux'
4 | import { useTranslation } from 'react-i18next'
5 | import { useHistory, useParams } from 'react-router'
6 | import { nodeSelectors } from '~store/types'
7 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
8 | import { clearQr } from '~store/modules/node/actions'
9 | import { NotFound } from '~util/errorpage'
10 |
11 | export const QrCodeViewVpn: React.FC<{}> = () => {
12 | const history = useHistory()
13 | const qrCode = useSelector(nodeSelectors.getCurrentQrCode)
14 | const { t } = useTranslation()
15 | const { netid, clientid } = useParams<{ netid: string; clientid: string }>()
16 | const dispatch = useDispatch()
17 |
18 | useLinkBreadcrumb({
19 | link: `/prouser/${netid}/vpnview`,
20 | title: clientid,
21 | })
22 |
23 | useLinkBreadcrumb({
24 | title: 'qr',
25 | })
26 |
27 | const handleClose = () => {
28 | dispatch(clearQr())
29 | history.goBack()
30 | }
31 |
32 | if (!qrCode) {
33 | return (
34 |
35 | )
36 | }
37 |
38 | const boxStyle = {
39 | modal: {
40 | position: 'absolute',
41 | display: 'flex',
42 | flexDirection: 'column',
43 | flex: 1,
44 | top: '50%',
45 | left: '50%',
46 | transform: 'translate(-50%, -50%)',
47 | width: 400,
48 | backgroundColor: 'white',
49 | border: '1px solid #000',
50 | minWidth: '33%',
51 | // boxShadow: 24,
52 | pt: 2,
53 | px: 4,
54 | pb: 3,
55 | },
56 | center: {
57 | textAlign: 'center',
58 | },
59 | max: {
60 | width: '75%',
61 | },
62 | } as any
63 |
64 | return (
65 |
66 |
67 |
68 |
69 |
70 | {`${t('extclient.qr')} : ${clientid}`}
71 |
72 |
73 |
74 |
79 |
80 |
81 |
82 |
83 | )
84 | }
85 |
--------------------------------------------------------------------------------
/src/proroute/proutils/ProConsts.tsx:
--------------------------------------------------------------------------------
1 | export const NET_ADMIN_ACCESS_LVL = 0
2 | export const NODE_ACCESS_LVL = 1
3 | export const CLIENT_ACCESS_LVL = 2
4 |
--------------------------------------------------------------------------------
/src/proroute/proutils/ProNetworkSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector, useDispatch } from 'react-redux'
3 | import { useHistory, useParams } from 'react-router-dom'
4 | import { proSelectors } from '~store/selectors'
5 | import { useTranslation } from 'react-i18next'
6 | import ProCustomSelect from './ProCustomSelect'
7 | import { FormControl, Grid, Typography } from '@mui/material'
8 | import { clearCurrentMetrics } from '~store/modules/server/actions'
9 | import { GenericError } from '~util/genericerror'
10 |
11 | import { authSelectors } from '~store/selectors'
12 |
13 | export const ProNetworkSelect: React.FC<{
14 | selectAll?: boolean
15 | prepend?: string
16 | }> = ({ selectAll, prepend }) => {
17 | const networkNames = useSelector(proSelectors.getNetworkUserNetworks)
18 | const { t } = useTranslation()
19 | const history = useHistory()
20 | const { netid } = useParams<{ netid?: string }>()
21 | const dispatch = useDispatch()
22 | const user = useSelector(authSelectors.getUser)
23 |
24 | if (selectAll && !!netid) {
25 | networkNames.push(t('common.selectall'))
26 | }
27 |
28 | const titleStyle = {
29 | textAlign: 'center',
30 | } as any
31 |
32 | // if statement for if no network display message no networks
33 | if (networkNames.length === 0) {
34 | return
35 | }
36 |
37 | return (
38 |
39 |
40 |
41 |
42 | {`${t('pro.label.welcome')} ${user?.name}`}
43 |
44 |
45 |
46 |
47 |
48 | {
51 | dispatch(clearCurrentMetrics())
52 | const netIndex = history.location.pathname.indexOf(netid!)
53 | if (netid === undefined) {
54 | history.push(`${history.location.pathname}/${selected}`)
55 | } else if (selectAll && selected === t('common.selectall')) {
56 | history.push(history.location.pathname.substr(0, netIndex - 1))
57 | } else if (netid !== undefined) {
58 | history.push(
59 | history.location.pathname.replace(netid!, selected)
60 | )
61 | }
62 | }}
63 | items={networkNames}
64 | />
65 |
66 |
67 |
68 | )
69 | }
70 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/route/PrivateRoute.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { Route, Redirect, RouteProps } from 'react-router-dom'
4 | import { User } from '~store/types'
5 | import { authSelectors } from '~store/selectors'
6 | import { LocationDescriptor } from 'history'
7 |
8 | interface PrivateRouteProps extends RouteProps {
9 | condition?: (user?: User) => boolean
10 | to?: LocationDescriptor
11 | }
12 |
13 | export const PrivateRoute: React.FC<
14 | React.PropsWithChildren
15 | > = ({ children, condition, to, ...rest }) => {
16 | const isLoggedIn = useSelector(authSelectors.getLoggedIn)
17 | const user = useSelector(authSelectors.getUser)
18 | const showRoute = condition ? condition(user) : isLoggedIn
19 | return (
20 | {
23 | return showRoute ? (
24 | children
25 | ) : (
26 |
36 | )
37 | }}
38 | />
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/route/accesskeys/AccessKeys.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { AccessKeyCreate } from './components/AccessKeyCreate'
7 | import { AccessKeyView } from './components/AccessKeyView'
8 | import { NetworkSelect } from '~components/NetworkSelect'
9 | import { AccessKeyTable } from './components/AccessKeyTable'
10 |
11 | export const AccessKeys: React.FC = () => {
12 | const { path } = useRouteMatch()
13 | const { t } = useTranslation()
14 |
15 | useLinkBreadcrumb({
16 | title: t('breadcrumbs.accessKeys'),
17 | })
18 |
19 | const titleStyle = {
20 | textAlign: 'center',
21 | } as any
22 |
23 | return (
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 | {t('accesskey.accesskeys')}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/src/route/accesskeys/components/AccessKeySelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { useRouteMatch, useHistory } from 'react-router-dom'
4 | import { networkSelectors } from '../../../store/selectors'
5 | import { useTranslation } from 'react-i18next'
6 | import CustomSelect from '../../../components/CustomSelect'
7 | import { Grid } from '@mui/material'
8 |
9 | export const AccessKeySelect: React.FC = () => {
10 | const networkNames = useSelector(networkSelectors.getNetworks).map(
11 | (net) => net.netid
12 | )
13 |
14 | const { path } = useRouteMatch()
15 | const { t } = useTranslation()
16 | const history = useHistory()
17 |
18 | return (
19 |
20 |
27 | {
30 | history.push(`${path}/${selected}`)
31 | }}
32 | items={networkNames}
33 | />
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/route/accesskeys/components/AccessKeyView.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { networkSelectors } from '~store/types'
4 | import { useHistory, useParams } from 'react-router'
5 | import AccessKeyDetails from './AccessKeyDetails'
6 | import { useTranslation } from 'react-i18next'
7 | import { NotFound } from '~util/errorpage'
8 |
9 | export const AccessKeyView: React.FC<{}> = () => {
10 | const history = useHistory()
11 | const { t } = useTranslation()
12 | const { netid, keyname } = useParams<{ netid: string; keyname: string }>()
13 | const networks = useSelector(networkSelectors.getNetworks)
14 | const netIndex = networks.findIndex((network) => network.netid === netid)
15 |
16 | if (!~netIndex) {
17 | return
18 | }
19 |
20 | const accessKeys = networks[netIndex].accesskeys
21 | const keyIndex = accessKeys.findIndex(
22 | (accessKey) => accessKey.name === keyname
23 | )
24 | const accessKeyInfo = accessKeys[keyIndex]
25 |
26 | const handleClose = () => history.goBack()
27 |
28 | return (
29 | {}}
33 | handleClose={handleClose}
34 | accessString={accessKeyInfo.accessstring}
35 | keyValue={accessKeyInfo.value}
36 | netID={netid}
37 | />
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/src/route/dashboard/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import NetworkCard from '~components/dashboard/NetworkCard'
7 | import NodeCard from '~components/dashboard/NodeCard'
8 | import ExtClientsCard from '~components/dashboard/ExtClientsCard'
9 | import DNSCard from '~components/dashboard/DNSCard'
10 | import UserCard from '~components/dashboard/UserCard'
11 | import AdminCard from '~components/dashboard/AdminCard'
12 | import { useSelector } from 'react-redux'
13 | import { authSelectors } from '~store/types'
14 | import ACLCard from '~components/dashboard/ACLCard'
15 | import GraphCard from '~components/dashboard/GraphCard'
16 | import './dashboard-styles.css'
17 | import EnrollmentKeyCard from '~components/dashboard/EnrollmentKeyCard'
18 |
19 | export const Dashboard: React.FC = () => {
20 | const { path } = useRouteMatch()
21 | const { t } = useTranslation()
22 | const user = useSelector(authSelectors.getUser)
23 |
24 | useLinkBreadcrumb({
25 | title: t('breadcrumbs.dashboard'),
26 | })
27 |
28 | return (
29 |
30 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {user?.isAdmin && (
54 | <>
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | >
68 | )}
69 |
70 |
71 |
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/src/route/dashboard/dashboard-styles.css:
--------------------------------------------------------------------------------
1 | .clickable:hover {
2 | transform: scale(1.05, 1.05);
3 | cursor: pointer;
4 | }
5 |
--------------------------------------------------------------------------------
/src/route/dns/DNS.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { NetworkSelect } from '~components/NetworkSelect'
7 | import { useSelector } from 'react-redux'
8 | import { serverSelectors } from '~store/types'
9 | import { DNSView } from './components/DNSView'
10 | import { DNSEntryCreate } from './components/DNSCreate'
11 |
12 | export const DNS: React.FC = () => {
13 | const { path } = useRouteMatch()
14 | const { t } = useTranslation()
15 | const hasDNS = useSelector(serverSelectors.getServerConfig).DNSMode
16 |
17 | useLinkBreadcrumb({
18 | title: t('breadcrumbs.dns'),
19 | })
20 |
21 | const titleStyle = {
22 | textAlign: 'center',
23 | } as any
24 |
25 | const DnsTitle = () => (
26 |
27 |
28 |
29 |
30 | {hasDNS ? t('dns.title') : t('dns.disabled')}
31 |
32 |
33 |
34 |
35 | )
36 |
37 | return (
38 |
39 |
40 |
41 |
42 | {hasDNS && }
43 |
44 |
45 | {!hasDNS ? : }
46 |
47 |
48 | {!hasDNS ? : }
49 |
50 |
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/src/route/dns/components/DNSSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTranslation } from 'react-i18next'
3 | import CustomSelect from '../../../components/CustomSelect'
4 | import { Grid } from '@mui/material'
5 |
6 | export const DNSSelect: React.FC<{
7 | nodeAddrs: Array
8 | onSelect: (selected: string) => void
9 | }> = ({ nodeAddrs, onSelect }) => {
10 | const { t } = useTranslation()
11 |
12 | return (
13 |
14 |
21 | {
24 | onSelect(selected.split(' ')[0])
25 | }}
26 | items={nodeAddrs}
27 | />
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/route/extclients/ExtClients.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | // import { NetworkCreate } from './create/NetworkCreate'
5 | import { useTranslation } from 'react-i18next'
6 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
7 | import { NetworkSelect } from './components/NetworkSelect'
8 | import { ExtClientView } from './components/ExtClientView'
9 | import { QrCodeView } from './components/QrCodeView'
10 | import { ExtClientEdit } from './components/ExtClientEdit'
11 |
12 | export const ExtClients: React.FC = () => {
13 | const { path } = useRouteMatch()
14 | const { t } = useTranslation()
15 |
16 | useLinkBreadcrumb({
17 | title: t('breadcrumbs.extClients'),
18 | })
19 |
20 | const titleStyle = {
21 | textAlign: 'center',
22 | } as any
23 |
24 | return (
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 | {t('extclient.extclients')}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/src/route/extclients/components/DeleteExtClientButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Delete } from '@mui/icons-material'
3 | import { IconButton, Tooltip } from '@mui/material'
4 | import { useDispatch } from 'react-redux'
5 | import { i18n } from '../../../i18n/i18n'
6 | import { deleteExternalClient } from '~store/modules/node/actions'
7 | import { ExternalClient } from '~store/types'
8 | import CustomizedDialogs from '~components/dialog/CustomDialog'
9 |
10 | export const DeleteExtClientButton: React.FC<{
11 | client: ExternalClient
12 | }> = ({ client }) => {
13 | const dispatch = useDispatch()
14 | const [open, setOpen] = React.useState(false)
15 |
16 | const handleDeleteClient = () => {
17 | dispatch(
18 | deleteExternalClient.request({
19 | clientName: client.clientid,
20 | netid: client.network,
21 | })
22 | )
23 | }
24 |
25 | const handleClose = () => setOpen(false)
26 |
27 | const handleOpen = () => setOpen(true)
28 |
29 | return (
30 | <>
31 |
38 |
42 |
43 |
44 |
45 |
46 | >
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/route/extclients/components/DownloadExtClientButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Download, QrCode2 } from '@mui/icons-material'
3 | import { IconButton, Tooltip } from '@mui/material'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import { i18n } from '../../../i18n/i18n'
6 | import { getExternalClientConf } from '~store/modules/node/actions'
7 | import { authSelectors, ExternalClient } from '~store/types'
8 | import { useHistory, useRouteMatch } from 'react-router'
9 |
10 | export const DownloadExtClientButton: React.FC<{
11 | client: ExternalClient
12 | type: 'qr' | 'file'
13 | }> = ({ client, type }) => {
14 | const dispatch = useDispatch()
15 | const token = useSelector(authSelectors.getToken)
16 | const history = useHistory()
17 | const { url } = useRouteMatch()
18 |
19 | const handleDownloadConf = () => {
20 | dispatch(
21 | getExternalClientConf.request({
22 | clientid: client.clientid,
23 | netid: client.network,
24 | type,
25 | token: token || '',
26 | })
27 | )
28 |
29 | if (type === 'qr') {
30 | history.push(`${url}/${client.clientid}/qr`)
31 | }
32 | }
33 |
34 | return (
35 |
43 |
44 | {type === 'file' ? : }
45 |
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/route/extclients/components/EditExtClientButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Tooltip } from '@mui/material'
3 | import { i18n } from '../../../i18n/i18n'
4 | import { ExternalClient } from '~store/types'
5 | import { useRouteMatch } from 'react-router'
6 | import { NmLink } from '~components/Link'
7 |
8 | export const EditExtClientButton: React.FC<{
9 | client: ExternalClient
10 | }> = ({ client }) => {
11 | const { url } = useRouteMatch()
12 |
13 | return (
14 |
18 |
19 | {client.clientid}
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/route/extclients/components/ExtClientCreateButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { AddCircleOutline } from '@mui/icons-material'
3 | import { IconButton, Tooltip } from '@mui/material'
4 | import { useDispatch } from 'react-redux'
5 | import { i18n } from '../../../i18n/i18n'
6 | import { createExternalClient } from '~store/modules/node/actions'
7 | import { Node } from '~store/types'
8 |
9 | export const ExtClientCreateButton: React.FC<{
10 | node: Node
11 | }> = ({ node }) => {
12 | const dispatch = useDispatch()
13 |
14 | const handleExtClientCreate = () => {
15 | dispatch(
16 | createExternalClient.request({
17 | netid: node.network,
18 | nodeid: node.id,
19 | })
20 | )
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/route/extclients/components/NetClientsView.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { useRouteMatch, useHistory } from 'react-router-dom'
4 | import { networkSelectors } from '../../../store/selectors'
5 | import { useTranslation } from 'react-i18next'
6 | import CustomSelect from '../../../components/CustomSelect'
7 | import { Grid } from '@mui/material'
8 |
9 | export const NetClientsView: React.FC = () => {
10 | const listOfNetworks = useSelector(networkSelectors.getNetworks)
11 | const networkNames = []
12 | if (listOfNetworks) {
13 | for (let i = 0; i < listOfNetworks.length; i++) {
14 | networkNames.push(listOfNetworks[i].netid)
15 | }
16 | }
17 | const { path } = useRouteMatch()
18 | const { t } = useTranslation()
19 | const history = useHistory()
20 |
21 | const centerStyle = {
22 | textAlign: 'center',
23 | } as any
24 |
25 | return (
26 |
27 |
28 | {
31 | history.push(`${path}/${selected}`)
32 | }}
33 | items={networkNames}
34 | />
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/route/extclients/components/NetworkSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { useRouteMatch, useHistory } from 'react-router-dom'
4 | import { networkSelectors } from '../../../store/selectors'
5 | import { useTranslation } from 'react-i18next'
6 | import CustomSelect from '../../../components/CustomSelect'
7 | import { Grid } from '@mui/material'
8 |
9 | export const NetworkSelect: React.FC = () => {
10 | const listOfNetworks = useSelector(networkSelectors.getNetworks)
11 | const networkNames = []
12 | if (listOfNetworks) {
13 | for (let i = 0; i < listOfNetworks.length; i++) {
14 | networkNames.push(listOfNetworks[i].netid)
15 | }
16 | }
17 | const { path } = useRouteMatch()
18 | const { t } = useTranslation()
19 | const history = useHistory()
20 |
21 | const centerStyle = {
22 | textAlign: 'center',
23 | } as any
24 |
25 | return (
26 |
27 |
28 | {
31 | history.push(`${path}/${selected}`)
32 | }}
33 | items={networkNames}
34 | />
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/route/extclients/components/QrCodeView.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Grid, Modal, Typography, Box } from '@mui/material'
3 | import { useDispatch, useSelector } from 'react-redux'
4 | import { useTranslation } from 'react-i18next'
5 | import { useHistory, useRouteMatch, useParams } from 'react-router'
6 | import { nodeSelectors } from '~store/types'
7 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
8 | import { clearQr } from '~store/modules/node/actions'
9 | import { NotFound } from '~util/errorpage'
10 |
11 | export const QrCodeView: React.FC<{}> = () => {
12 | const history = useHistory()
13 | const qrCode = useSelector(nodeSelectors.getCurrentQrCode)
14 | const { t } = useTranslation()
15 | const { path } = useRouteMatch()
16 | const { netid, clientid } = useParams<{ netid: string; clientid: string }>()
17 | const newPath = `${path.split(':netid')[0]}${netid}`
18 | const dispatch = useDispatch()
19 |
20 | useLinkBreadcrumb({
21 | link: newPath,
22 | title: clientid,
23 | })
24 |
25 | useLinkBreadcrumb({
26 | title: 'qr',
27 | })
28 |
29 | const handleClose = () => {
30 | dispatch(clearQr())
31 | history.push(newPath)
32 | }
33 |
34 | if (!qrCode) {
35 | return
36 | }
37 |
38 | const boxStyle = {
39 | modal: {
40 | position: 'absolute',
41 | display: 'flex',
42 | flexDirection: 'column',
43 | flex: 1,
44 | top: '50%',
45 | left: '50%',
46 | transform: 'translate(-50%, -50%)',
47 | width: 400,
48 | backgroundColor: 'white',
49 | border: '1px solid #000',
50 | minWidth: '33%',
51 | // boxShadow: 24,
52 | pt: 2,
53 | px: 4,
54 | pb: 3,
55 | },
56 | center: {
57 | textAlign: 'center',
58 | },
59 | max: {
60 | width: '75%',
61 | },
62 | } as any
63 |
64 | return (
65 |
66 |
67 |
68 |
69 |
70 | {`${t('extclient.qr')} : ${clientid}`}
71 |
72 |
73 |
74 |
79 |
80 |
81 |
82 |
83 | )
84 | }
85 |
--------------------------------------------------------------------------------
/src/route/graph/Graphs.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { NetworkSelect } from '~components/NetworkSelect'
7 | // import { NetworkGraph } from './components/NetworkGraph'
8 | import { NetworkGraph } from './components/NetworkGraph'
9 |
10 | export const Graphs: React.FC = () => {
11 | const { path } = useRouteMatch()
12 | const { t } = useTranslation()
13 |
14 | useLinkBreadcrumb({
15 | title: t('breadcrumbs.graphs'),
16 | })
17 |
18 | const titleStyle = {
19 | textAlign: 'center',
20 | } as any
21 |
22 | return (
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 | {t('network.graphs')}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/route/graph/components/graph-components/types.tsx:
--------------------------------------------------------------------------------
1 | import { NodeConnectivityStatus } from "~store/types"
2 |
3 | // interfaces and types for use in graph-components
4 | export interface DataNode {
5 | type: DataNodeType
6 | id: string
7 | name: string
8 | lastCheckin: number | undefined
9 | address?: string
10 | address6?: string
11 | }
12 |
13 | export interface AltDataNode {
14 | id: string
15 | name: string
16 | type: 'extclient' | 'cidr'
17 | address?: string
18 | address6?: string
19 | }
20 |
21 | export interface Edge {
22 | from: string
23 | to: string
24 | status: NodeConnectivityStatus
25 | }
26 |
27 | export type DataNodeType = 'normal' | '1&e' | 'ingress' | 'egress' | 'i&r' | 'e&r' | 'relay' | 'relayed' | 'extclient' | 'cidr' | 'i&e&r'
28 |
--------------------------------------------------------------------------------
/src/route/graph/components/graph-components/util.tsx:
--------------------------------------------------------------------------------
1 | import { DataNodeType } from "./types"
2 | import { isNodeHealthy } from "~util/fields"
3 | import { NodeConnectivityStatus } from "~store/types"
4 |
5 | // util functions for graph nodes
6 | export const getNodeColor = (dataType: DataNodeType, lastcheckin: number | undefined) => {
7 | if (!!lastcheckin) {
8 | switch(isNodeHealthy(lastcheckin)) {
9 | case 1: return "#ff9800"
10 | case 2: return "#f44336"
11 | }
12 | }
13 | switch(dataType) {
14 | case 'normal': return "#2b00ff"
15 | case '1&e': return '#d9ffa3'
16 | case 'e&r': return '#19ffb2'
17 | case 'i&r': return '#d5db8a'
18 | case 'egress': return '#6bdbb6'
19 | case 'ingress': return '#ebde34'
20 | case 'extclient': return '#26ffff'
21 | case 'relay': return '#a552ff'
22 | case 'relayed': return '#639cbf'
23 | case 'cidr': return '#6fa397'
24 | case 'i&e&r': return '#f2c7ff'
25 | default: return "#2b00ff"
26 | }
27 | }
28 |
29 | /**
30 | * Get edge color based on edge connectivity status.
31 | *
32 | * @param {NodeConnectivityStatus} status status of the edge
33 | * @returns hex color code
34 | */
35 | export const getEdgeColor = (status: NodeConnectivityStatus): string => {
36 | const RED_COLOR = '#CC0000'
37 | const YELLOW_COLOR = '#CCCC00'
38 | const GREEN_COLOR = '#00CC00'
39 | const WHITE_COLOR = '#FFFFFF'
40 |
41 | switch (status) {
42 | case 'healthy':
43 | return GREEN_COLOR
44 | case 'warning':
45 | return YELLOW_COLOR
46 | case 'error':
47 | return RED_COLOR
48 | default:
49 | return WHITE_COLOR
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/route/hosts/HostsPage.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useCallback, useEffect } from 'react'
2 | import { Container, Grid, Typography } from '@mui/material'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { HostDetailPage } from './HostDetailPage'
7 | import { HostsTable } from './components/HostsTable'
8 | import { getHosts, updateHost } from '~store/modules/hosts/actions'
9 | import { useDispatch, useSelector } from 'react-redux'
10 | import { hostsSelectors } from '~store/selectors'
11 | import { Host } from '~store/types'
12 |
13 | export const HostsPage: FC = () => {
14 | const { path } = useRouteMatch()
15 | const { t } = useTranslation()
16 | const dispatch = useDispatch()
17 | const hosts = useSelector(hostsSelectors.getHosts)
18 |
19 | useLinkBreadcrumb({
20 | title: t('breadcrumbs.hosts'),
21 | })
22 |
23 | const titleStyle = {
24 | textAlign: 'center',
25 | marginBottom: '2rem',
26 | } as any
27 |
28 | const refreshHosts = useCallback(() => {
29 | dispatch(getHosts.request())
30 | }, [dispatch])
31 |
32 | const onToggleDefaultness = useCallback((host: Host) => {
33 | dispatch(updateHost.request({ ...host, isdefault: !host.isdefault }))
34 | }, [dispatch])
35 |
36 | // on created
37 | useEffect(() => {
38 | refreshHosts()
39 | }, [refreshHosts])
40 |
41 | return (
42 |
43 |
44 | {/* all hosts page */}
45 |
46 |
52 |
53 |
54 | {t('hosts.hosts')}
55 |
56 |
57 |
61 |
62 |
63 |
64 | {/* host details page */}
65 |
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/src/route/login/Login.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { Modal, Box, useTheme } from '@mui/material'
4 | import { useHistory } from 'react-router-dom'
5 | import { authSelectors } from '../../store/selectors'
6 | import ProLogin from '../../proroute/prologin/Login'
7 | import CreateAdmin from '../../proroute/prologin/CreateAdmin'
8 |
9 | const styles = {
10 | centerText: {
11 | textAlign: 'center',
12 | },
13 | vertTabs: {
14 | flex: 1,
15 | display: 'flex',
16 | flexDirection: 'column',
17 | position: 'relative',
18 | },
19 | mainContainer: {
20 | marginTop: '2em',
21 | display: 'flex',
22 | justifyContent: 'center',
23 | alignItems: 'center',
24 | },
25 | center: {
26 | flex: 1,
27 | display: 'flex',
28 | textAlign: 'center',
29 | },
30 | modal: {
31 | position: 'absolute',
32 | display: 'flex',
33 | flexDirection: 'column',
34 | flex: 1,
35 | top: '50%',
36 | left: '50%',
37 | transform: 'translate(-50%, -50%)',
38 | width: 400,
39 | backgroundColor: 'white',
40 | border: '1px solid #000',
41 | minWidth: '33%',
42 | // boxShadow: 24,
43 | pt: 2,
44 | px: 4,
45 | pb: 3,
46 | },
47 | } as any
48 |
49 | export function Login() {
50 | const history = useHistory()
51 | const hasAdmin = useSelector(authSelectors.hasAdmin)
52 | const theme = useTheme()
53 |
54 | const authRenderSwitch = () => {
55 | if (hasAdmin) return
56 | return
57 | }
58 |
59 | return (
60 | {
64 | const ignorableReasons = ['escapeKeyDown', 'backdropClick']
65 | if (ignorableReasons.includes(reason)) {
66 | return
67 | }
68 | history.goBack()
69 | }}
70 | >
71 |
77 | {authRenderSwitch()}
78 |
79 |
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/src/route/networks/networkId/components/NetworkModifiedStats.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, TextField } from '@mui/material'
2 | import { useTranslation } from 'react-i18next'
3 | import { datePickerConverter } from '~util/unixTime'
4 | import { useNetwork } from '~util/network'
5 |
6 | export const NetworkModifiedStats: React.FC<{ netid: string }> = ({
7 | netid,
8 | }) => {
9 | const network = useNetwork(netid)
10 | const { t } = useTranslation()
11 |
12 | const fieldStyle = {
13 | marginTop: '1em',
14 | }
15 |
16 | if (!network) {
17 | return null
18 | }
19 | return (
20 |
27 |
28 |
38 |
39 |
40 |
41 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/src/route/networkusers/NetworkUsers.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { NetworkSelect } from '~components/NetworkSelect'
7 | import { NetworkUsersTable } from './components/NetworkUsersTable'
8 | import { NetworkUserEdit } from './components/NetworkUserEdit'
9 |
10 | export const NetworkUsers: React.FC = () => {
11 | const { path } = useRouteMatch()
12 | const { t } = useTranslation()
13 |
14 | useLinkBreadcrumb({
15 | title: t('pro.label.userpermissions'),
16 | })
17 |
18 | const titleStyle = {
19 | textAlign: 'center',
20 | } as any
21 |
22 | return (
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 | {t('pro.label.userpermissions')}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/src/route/node_acls/NodeACLs.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { NetworkSelect } from '~components/NetworkSelect'
7 | import { NodesACLTable } from './components/NodesACLTable'
8 |
9 | export const NodeAcls: React.FC = () => {
10 | const { path, url } = useRouteMatch()
11 | const { t } = useTranslation()
12 |
13 | useLinkBreadcrumb({
14 | link: url,
15 | title: t('breadcrumbs.acls'),
16 | })
17 |
18 | const titleStyle = {
19 | textAlign: 'center',
20 | } as any
21 |
22 | return (
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 | {t('acls.nodes')}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/route/nodes/netid/components/HubButton.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { useTranslation } from 'react-i18next'
4 | import { IconButton, Tooltip } from '@mui/material'
5 | import { updateNode } from '~modules/node/actions'
6 | import { hostsSelectors, Node } from '~store/types'
7 | import CustomDialog from '~components/dialog/CustomDialog'
8 |
9 | const hoverBlueStyle = {
10 | ':hover': {
11 | color: '#3f51b5',
12 | },
13 | }
14 |
15 | export const HubButton: React.FC<{
16 | node: Node
17 | createText: string
18 | disabledText: string
19 | SignalIcon: ReactNode
20 | children?: ReactNode
21 | extraLogic?: () => void
22 | }> = ({ node, createText, disabledText, SignalIcon, extraLogic }) => {
23 | const dispatch = useDispatch()
24 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
25 | const [hovering, setHovering] = React.useState(false)
26 | const [open, setOpen] = React.useState(false)
27 | const { t } = useTranslation()
28 | const hostsMap = useSelector(hostsSelectors.getHostsMap)
29 | const handleHoverEnter = () => setHovering(true)
30 | const handleHoverLeave = () => setHovering(false)
31 |
32 | const handleOpen = () => setOpen(true)
33 | const handleClose = () => setOpen(false)
34 |
35 | const createHub = () => {
36 | dispatch(
37 | updateNode.request({
38 | netid: node.network,
39 | token: '',
40 | node: {
41 | ...node,
42 | },
43 | })
44 | )
45 | if (!!extraLogic) {
46 | extraLogic()
47 | }
48 | handleClose()
49 | }
50 |
51 | return (
52 | <>
53 |
60 |
61 |
62 |
69 | {SignalIcon}
70 |
71 |
72 |
73 | >
74 | )
75 | }
76 |
--------------------------------------------------------------------------------
/src/route/usergroups/UserGroups.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Typography } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { NmLink } from '~components/index'
7 | import { UserGroupsTable } from './table/UserGroupsTable'
8 | import { UserGroupCreate } from './create/UserGroupCreate'
9 |
10 | export const UserGroups: React.FC = () => {
11 | const { path } = useRouteMatch()
12 | const { t } = useTranslation()
13 |
14 | useLinkBreadcrumb({
15 | title: t('pro.label.usergroups'),
16 | })
17 |
18 | return (
19 |
20 |
21 |
22 |
29 |
30 | {t('pro.label.usergroups')}
31 |
32 |
33 |
34 | {`${t('common.create')} ${t('pro.label.usergroup')}`}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/route/usergroups/table/UserGroupsTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { proSelectors } from '~store/selectors'
4 | import { NmTable, TableColumns } from '~components/Table'
5 | import { Delete } from '@mui/icons-material'
6 | import { useDialog } from '~components/ConfirmDialog'
7 | import { useTranslation } from 'react-i18next'
8 | import { deleteUserGroup, getUserGroups } from '~store/modules/pro/actions'
9 | import { IconButton, Tooltip } from '@mui/material'
10 |
11 | type UserGroup = {
12 | groupname: string;
13 | action?: any;
14 | }
15 |
16 | export const UserGroupsTable: React.FC = () => {
17 | const userGroups = useSelector(proSelectors.userGroups)
18 | const { Component: Dialog, setProps } = useDialog()
19 | const { t } = useTranslation()
20 | const dispatch = useDispatch()
21 | const currentGroups = [] as UserGroup[]
22 |
23 | if (!!userGroups && !!userGroups.length) {
24 | for (let i = 0; i < userGroups.length; i++) {
25 | currentGroups.push({groupname: userGroups[i]})
26 | }
27 | }
28 |
29 | React.useEffect(() => {
30 | if (!!!userGroups || !!!userGroups.length) {
31 | dispatch(getUserGroups.request())
32 | }
33 | }, [userGroups, dispatch])
34 |
35 | const columns: TableColumns = [
36 | {
37 | id: 'groupname',
38 | labelKey: 'pro.label.usergroup',
39 | minWidth: 200,
40 | sortable: true,
41 | align: 'center',
42 | },
43 | {
44 | id: 'action',
45 | labelKey: 'common.delete',
46 | minWidth: 100,
47 | align: 'center',
48 | format: ((_, row) =>
49 | {
52 | setProps({
53 | message: `${t('common.delete')} ${t('pro.label.usergroup')} "${row.groupname}"`,
54 | title: t('common.submit'),
55 | onSubmit: () => {
56 | dispatch(
57 | deleteUserGroup.request({
58 | groupName: row.groupname
59 | })
60 | )},
61 | })
62 | }} >
63 |
64 |
65 | )
66 | },
67 | ]
68 |
69 | return (
70 | <>
71 | `${row.groupname}-${i}`}
75 | />
76 |
77 | >
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/src/route/users/UsersCommunity.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid } from '@mui/material'
2 | import React from 'react'
3 | import { useRouteMatch, Switch, Route } from 'react-router-dom'
4 | import { useTranslation } from 'react-i18next'
5 | import { useLinkBreadcrumb } from '~components/PathBreadcrumbs'
6 | import { NmLink } from '~components/index'
7 | import { UserTableCommunity } from './components/UserTableCommunity'
8 | import { UserCreate } from './create/UserCreate'
9 | import { UserEditCommunity } from './userId/UserEditCommunity'
10 | import { UserChangePassword } from './userId/UserChangePassword'
11 |
12 | export const UsersCommunity: React.FC = () => {
13 | const { path } = useRouteMatch()
14 | const { t } = useTranslation()
15 |
16 | useLinkBreadcrumb({
17 | title: t('breadcrumbs.users'),
18 | })
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
32 |
33 | {t('users.header')}
34 |
35 |
36 |
37 | {t('users.create.button')}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/src/route/users/components/UserTableCommunity.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { authSelectors } from '~store/selectors'
4 | import { NmLink } from '~components/Link'
5 | import { NmTable, TableColumns } from '~components/Table'
6 | import { User } from '~store/types'
7 | import { Delete } from '@mui/icons-material'
8 | import { useDialog } from '~components/ConfirmDialog'
9 | import { useTranslation } from 'react-i18next'
10 | import { deleteUser } from '~store/modules/auth/actions'
11 |
12 | const columns: TableColumns = [
13 | {
14 | id: 'name',
15 | labelKey: 'users.table.name',
16 | minWidth: 170,
17 | sortable: true,
18 | format: (username) => {username},
19 | },
20 | {
21 | id: 'isAdmin',
22 | labelKey: 'users.table.isAdmin',
23 | minWidth: 100,
24 | sortable: true,
25 | format: (isAdmin) => (isAdmin ? 'True' : 'False'),
26 | },
27 | {
28 | id: 'networks',
29 | labelKey: 'users.table.networks',
30 | minWidth: 150,
31 | sortable: false,
32 | format: (networks, user) => {
33 | if (user.isAdmin && (!networks || !networks.length)) return *
34 | return (
35 |
36 | {networks?.map((network) => (
37 |
38 | {network}
39 |
40 | ))}
41 |
42 | )
43 | },
44 | },
45 | ]
46 |
47 | export const UserTableCommunity: React.FC = () => {
48 | const users = useSelector(authSelectors.getUsers)
49 | const { Component: Dialog, setProps } = useDialog()
50 | const { t } = useTranslation()
51 | const dispatch = useDispatch()
52 |
53 | return (
54 | <>
55 | row.name}
59 | actions={[
60 | (row) => ({
61 | tooltip: t('common.delete'),
62 | disabled: false,
63 | icon: ,
64 | onClick: () => {
65 | setProps({
66 | message: t('users.delete'),
67 | title: t('users.deleteTitle'),
68 | onSubmit: () =>
69 | dispatch(
70 | deleteUser.request({
71 | username: row.name,
72 | })
73 | ),
74 | })
75 | },
76 | }),
77 | ]}
78 | />
79 |
80 | >
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect'
6 |
--------------------------------------------------------------------------------
/src/store/actions.ts:
--------------------------------------------------------------------------------
1 | import { ActionType } from 'typesafe-actions'
2 |
3 | import { actions as auth } from './modules/auth'
4 | import { actions as network } from './modules/network'
5 | import { actions as node } from './modules/node'
6 | import { actions as server } from './modules/server'
7 | import { actions as toast } from './modules/toast'
8 | import { actions as router } from './modules/router'
9 | import { actions as acls } from './modules/acls'
10 | import { actions as pro } from './modules/pro'
11 | import { actions as hosts } from './modules/hosts'
12 | import { actions as enrollmentKeys } from './modules/enrollmentkeys'
13 |
14 | export const actions = {
15 | auth,
16 | network,
17 | node,
18 | server,
19 | toast,
20 | router,
21 | acls,
22 | pro,
23 | hosts,
24 | enrollmentKeys,
25 | }
26 |
27 | export type RootAction = ActionType
28 |
--------------------------------------------------------------------------------
/src/store/createStore.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useSelector } from 'react-redux'
2 | import { applyMiddleware, createStore } from 'redux'
3 | import { createLogger } from 'redux-logger'
4 | import createSagaMiddleware from 'redux-saga'
5 | import { DEBUG } from '../config'
6 |
7 | import { RootAction as RA } from './actions'
8 | import { createRootReducer, RootState as RS } from './reducers'
9 | import { createRootSaga } from './sagas'
10 |
11 | declare module 'typesafe-actions' {
12 | export type RootAction = RA
13 | export type RootState = RS
14 | interface Types {
15 | RootAction: RootAction
16 | RootState: RootState
17 | }
18 | }
19 |
20 | const logger = createLogger({
21 | level: 'info',
22 | })
23 |
24 | export const createReduxStore = () => {
25 | const sagaMiddleware = createSagaMiddleware()
26 | let enhancer
27 | if (DEBUG) {
28 | enhancer = applyMiddleware(logger, sagaMiddleware)
29 | } else {
30 | enhancer = applyMiddleware(sagaMiddleware)
31 | }
32 | const store = createStore(createRootReducer(), enhancer)
33 | sagaMiddleware.run(createRootSaga())
34 | return store
35 | }
36 |
37 | export const useMintableSelector: TypedUseSelectorHook = useSelector
38 |
--------------------------------------------------------------------------------
/src/store/modules/acls/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createAsyncAction } from 'typesafe-actions'
2 | import { GetACLContainerPayload, UpdateNodeACLContainerPayload, UpdateNodeACLPayload } from '.'
3 |
4 | export const getNodeACLContainer = createAsyncAction(
5 | 'ACLs_getNodeACLContainer_Request',
6 | 'ACLs_getNodeACLContainer_Success',
7 | 'ACLs_getNodeACLContainer_Failure'
8 | )()
9 |
10 | export const updateNodeContainerACL = createAsyncAction(
11 | 'ACLs_updateNodeACLContainer_Request',
12 | 'ACLs_updateNodeACLContainer_Success',
13 | 'ACLs_updateNodeACLContainer_Failure'
14 | )()
15 |
16 | export const updateNodeACL = createAsyncAction(
17 | 'ACLs_updateNodeACL_Request',
18 | 'ACLs_updateNodeACL_Success',
19 | 'ACLs_updateNodeACL_Failure'
20 | )()
21 |
22 | export const clearCurrentACL = createAction('clearCurrentACLContainer')()
23 |
--------------------------------------------------------------------------------
/src/store/modules/acls/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/acls/reducer.ts:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 | import { createReducer } from 'typesafe-actions'
3 | import {
4 | updateNodeContainerACL,
5 | getNodeACLContainer,
6 | clearCurrentACL,
7 | } from './actions'
8 | import {
9 | NodeACLContainer
10 | } from './types'
11 |
12 | export const reducer = createReducer({
13 | isProcessing: false as boolean,
14 | currentACL: {} as NodeACLContainer,
15 | })
16 | .handleAction(clearCurrentACL, (state, _) =>
17 | produce(state, (draftState) => {
18 | draftState.isProcessing = false
19 | draftState.currentACL = {}
20 | })
21 | )
22 | .handleAction(getNodeACLContainer['request'], (state, _) =>
23 | produce(state, (draftState) => {
24 | draftState.isProcessing = true
25 | })
26 | )
27 | .handleAction(getNodeACLContainer['success'], (state, payload) =>
28 | produce(state, (draftState) => {
29 | draftState.isProcessing = false
30 | draftState.currentACL = payload.payload
31 | })
32 | )
33 | .handleAction(getNodeACLContainer['failure'], (state, _) =>
34 | produce(state, (draftState) => {
35 | draftState.isProcessing = false
36 | })
37 | )
38 | .handleAction(updateNodeContainerACL['request'], (state, _) =>
39 | produce(state, (draftState) => {
40 | draftState.isProcessing = true
41 | })
42 | )
43 | .handleAction(updateNodeContainerACL['success'], (state, payload) =>
44 | produce(state, (draftState) => {
45 | draftState.isProcessing = false
46 | draftState.currentACL = payload.payload
47 | })
48 | )
49 | .handleAction(updateNodeContainerACL['failure'], (state, _) =>
50 | produce(state, (draftState) => {
51 | draftState.isProcessing = false
52 | })
53 | )
54 | // .handleAction(updateNodeACL['request'], (state, _) =>
55 | // produce(state, (draftState) => {
56 | // draftState.isProcessing = true
57 | // })
58 | // )
59 | // .handleAction(updateNodeACL['success'], (state, payload) =>
60 | // produce(state, (draftState) => {
61 | // draftState.isProcessing = false
62 | // const { nodeID, nodeACL } = payload.payload
63 | // draftState.currentACL[nodeID] = nodeACL
64 | // })
65 | // )
66 | // .handleAction(updateNodeACL['failure'], (state, _) =>
67 | // produce(state, (draftState) => {
68 | // draftState.isProcessing = false
69 | // })
70 | // )
71 |
--------------------------------------------------------------------------------
/src/store/modules/acls/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getACLState = (state: RootState) => state.acls
5 |
6 | export const isProcessing = createSelector(
7 | getACLState,
8 | (acl) => acl.isProcessing
9 | )
10 | export const getCurrentACL = createSelector(getACLState, (acl) => acl.currentACL)
11 |
--------------------------------------------------------------------------------
/src/store/modules/acls/types.ts:
--------------------------------------------------------------------------------
1 | export type NodeID = string
2 |
3 | export type NodeACL = Record
4 |
5 | export type MutableRequired = { -readonly [P in keyof T]-?: T[P] };
6 |
7 | export type NodeACLContainer = MutableRequired<{ [nodeID: NodeID] : NodeACL}>
8 |
9 | export interface UpdateNodeACLPayload {
10 | Request: {
11 | netid: string
12 | nodeid: NodeID
13 | nodeACL: NodeACL
14 | }
15 | Response: {
16 | nodeID: NodeID
17 | nodeACL: NodeACL
18 | }
19 | }
20 |
21 | export interface UpdateNodeACLContainerPayload {
22 | Request: {
23 | netid: string
24 | aclContainer: NodeACLContainer
25 | }
26 | Response: NodeACLContainer
27 | }
28 |
29 | export interface GetACLContainerPayload {
30 | Request: {
31 | netid: string
32 | }
33 | Response: NodeACLContainer
34 | }
35 |
--------------------------------------------------------------------------------
/src/store/modules/api/index.ts:
--------------------------------------------------------------------------------
1 | export { reducer } from './reducer'
2 | export { saga } from './saga'
3 | export * as selectors from './selectors'
4 | export * from './types'
5 |
--------------------------------------------------------------------------------
/src/store/modules/api/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from 'typesafe-actions'
2 | import axios from 'axios'
3 | import { BACKEND_URL } from '../../../config'
4 |
5 | export const reducer = createReducer({
6 | axios: axios.create({
7 | baseURL: `${BACKEND_URL}/api`,
8 | }),
9 | })
10 |
--------------------------------------------------------------------------------
/src/store/modules/api/saga.ts:
--------------------------------------------------------------------------------
1 | import { select, all, call, put } from 'redux-saga/effects'
2 | import { produce } from 'immer'
3 | import { getToken, getLoggedIn } from '../auth/selectors'
4 | import { getApi } from './selectors'
5 | import { AxiosResponse, AxiosInstance, AxiosRequestConfig } from 'axios'
6 | import { KeysOfAxios, ApiRequestSignatureGenerator } from './types'
7 | import { logout } from '../auth/actions'
8 | import { i18n } from '../../../i18n/i18n'
9 |
10 | export function* apiRequestSaga(
11 | method: K,
12 | ...params: Required>
13 | ) {
14 | const api: ReturnType = yield select(getApi)
15 | return (yield call(
16 | api[method],
17 | ...(params as unknown as Parameters)
18 | )) as AxiosResponse
19 | }
20 |
21 | export function* apiRequestWithAuthSaga(
22 | method: K,
23 | ...params: Required>
24 | ) {
25 | const token: ReturnType = yield select(getToken)
26 | const isLoggedIn: boolean = yield select(getLoggedIn)
27 | if(!isLoggedIn && !!token) {
28 | yield put(logout())
29 | throw new Error(i18n.t('error.tokenexpire'))
30 | }
31 | let config = params.pop() as AxiosRequestConfig
32 | config = produce(config, (draft) => {
33 | draft.headers = draft.headers ? draft.headers : {}
34 | draft.headers['authorization'] = `Bearer ${token}`
35 | })
36 | params.push(config)
37 | return (yield call>(
38 | apiRequestSaga,
39 | method,
40 | ...params
41 | )) as AxiosResponse
42 | }
43 |
44 | export function* saga() {
45 | yield all([])
46 | }
47 |
--------------------------------------------------------------------------------
/src/store/modules/api/selectors.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from '../../reducers'
2 |
3 | export const getApi = (state: RootState) => state.api.axios
4 |
--------------------------------------------------------------------------------
/src/store/modules/api/types.ts:
--------------------------------------------------------------------------------
1 | import { AxiosInstance, AxiosResponse } from 'axios'
2 | import { SelectEffect, CallEffect, SagaReturnType } from 'redux-saga/effects'
3 |
4 | export type KeyOf = {
5 | [K in keyof T]: T[K] extends I ? K : never
6 | }[keyof T]
7 | export type KeysOfAxios = KeyOf<
8 | AxiosInstance,
9 | (url: string, ...args: any[]) => Promise>
10 | >
11 |
12 | export type ApiRequestSignatureGenerator = (
13 | method: K,
14 | ...params: Required>
15 | ) => Generator<
16 | SelectEffect | CallEffect>,
17 | AxiosResponse,
18 | AxiosInstance & AxiosResponse
19 | >
20 |
--------------------------------------------------------------------------------
/src/store/modules/auth/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createAsyncAction } from 'typesafe-actions'
2 | import { UserSettings } from '.'
3 | import {
4 | CreateAdmin,
5 | CreateUser,
6 | DeleteUser,
7 | GetAllUsers,
8 | GetUser,
9 | HasAdmin,
10 | Login,
11 | UpdateUser,
12 | UpdateUserNetworks,
13 | User,
14 | } from './types'
15 |
16 | export const setUser = createAction('setUser')()
17 |
18 | export const setUserSettings = createAction('setUserSettings')()
19 |
20 | export const setAuthError = createAction('setAuthError')()
21 |
22 | export const logout = createAction('logout')()
23 |
24 | export const getAllUsers = createAsyncAction(
25 | 'api_getAllUsers_Request',
26 | 'api_getAllUsers_Success',
27 | 'api_getAllUsers_Failure'
28 | )()
29 |
30 | export const getUser = createAsyncAction(
31 | 'api_getUser_Request',
32 | 'api_getUser_Success',
33 | 'api_getUser_Failure'
34 | )()
35 |
36 | export const login = createAsyncAction(
37 | 'api_login_Request',
38 | 'api_login_Success',
39 | 'api_login_Failure'
40 | )()
41 |
42 | export const hasAdmin = createAsyncAction(
43 | 'api_hasAdmin_Request',
44 | 'api_hasAdmin_Success',
45 | 'api_hasAdmin_Failure'
46 | )()
47 |
48 | export const createAdmin = createAsyncAction(
49 | 'api_createAdmin_Request',
50 | 'api_createAdmin_Success',
51 | 'api_createAdmin_Failure'
52 | )()
53 |
54 | export const createUser = createAsyncAction(
55 | 'api_createUser_Request',
56 | 'api_createUser_Success',
57 | 'api_createUser_Failure'
58 | )()
59 |
60 | export const deleteUser = createAsyncAction(
61 | 'api_deleteUser_Request',
62 | 'api_deleteUser_Success',
63 | 'api_deleteUser_Failure'
64 | )()
65 |
66 | export const updateUser = createAsyncAction(
67 | 'api_updateUser_Request',
68 | 'api_updateUser_Success',
69 | 'api_updateUser_Failure'
70 | )()
71 |
72 | export const updateUserNetworks = createAsyncAction(
73 | 'api_updateUserNetworks_Request',
74 | 'api_updateUserNetworks_Success',
75 | 'api_updateUserNetworks_Failure'
76 | )()
77 |
--------------------------------------------------------------------------------
/src/store/modules/auth/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/auth/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getAuth = (state: RootState) => state.auth
5 |
6 | export const getToken = createSelector(getAuth, (auth) => auth.token)
7 | export const getUser = createSelector(getAuth, (auth) => auth.user)
8 | export const getLoggedIn = createSelector(
9 | getAuth,
10 | (auth) => !!auth.user && Date.now() / 1000 < auth.user.exp
11 | )
12 | export const isLogginIn = createSelector(getAuth, (auth) => auth.isLoggingIn)
13 | export const hasAdmin = createSelector(getAuth, (auth) => auth.hasAdmin)
14 | export const isCreating = createSelector(getAuth, (auth) => auth.isCreating)
15 | export const getUsers = createSelector(getAuth, (auth) => auth.users)
16 | export const hasNetworkError = createSelector(
17 | getAuth,
18 | (auth) => auth.networkError
19 | )
20 | export const getAuthError = createSelector(getAuth, (auth) => auth.authError)
21 | export const getUserSettings = createSelector(
22 | getAuth,
23 | (auth) => auth.userSettings
24 | )
25 | export const isInDarkMode = createSelector(
26 | getAuth,
27 | (auth) => !!!auth.userSettings.mode || auth.userSettings.mode === 'dark'
28 | )
29 |
--------------------------------------------------------------------------------
/src/store/modules/auth/types.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | name: string
3 | isAdmin: boolean
4 | exp: number
5 | networks: null | Array
6 | groups: null | Array
7 | }
8 |
9 | export interface LocalStorageUserKeyValue {
10 | token: string
11 | user: User
12 | }
13 |
14 | export interface UserSettings {
15 | rowsPerPage: number
16 | username: string
17 | mode: 'dark' | 'light' | undefined
18 | }
19 |
20 | export interface LocalSettings {
21 | userSettings: UserSettings[]
22 | }
23 |
24 | export interface GetAllUsers {
25 | Request: void
26 | Response: Array
27 | }
28 |
29 | export interface GetUser {
30 | Request: void
31 | Response: User
32 | }
33 |
34 | export interface Login {
35 | Request: {
36 | username: string
37 | password: string
38 | }
39 | Response: {
40 | token: string
41 | }
42 | }
43 |
44 | export interface HasAdmin {
45 | Request: void
46 | Response: boolean
47 | }
48 |
49 | export interface CreateAdmin {
50 | Request: {
51 | username: string
52 | password: string
53 | }
54 | Response: {}
55 | }
56 |
57 | export interface CreateUser {
58 | Request: {
59 | username: string
60 | password: string
61 | isadmin: boolean
62 | networks: Array
63 | }
64 | Response: Omit
65 | }
66 |
67 | export interface DeleteUser {
68 | Request: {
69 | username: string
70 | }
71 | Response: {
72 | username: string
73 | }
74 | }
75 |
76 | export interface UpdateUser {
77 | Request: {
78 | username: string
79 | password: string
80 | }
81 | Response: User
82 | }
83 |
84 | export interface UpdateUserNetworks {
85 | Request: {
86 | username: string
87 | isadmin: boolean
88 | networks: Array
89 | groups: Array
90 | }
91 | Response: UpdateUserNetworks['Request']
92 | }
93 |
--------------------------------------------------------------------------------
/src/store/modules/enrollmentkeys/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createAsyncAction } from 'typesafe-actions'
2 | import {
3 | GetEnrollmentKeysPayload,
4 | DeleteEnrollmentKeyPayload,
5 | CreateEnrollmentKeyPayload,
6 | } from '.'
7 |
8 | export const getEnrollmentKeys = createAsyncAction(
9 | 'EnrollmentKeys_getEnrollmentKeys_Request',
10 | 'EnrollmentKeys_getEnrollmentKeys_Success',
11 | 'EnrollmentKeys_getEnrollmentKeys_Failure'
12 | )()
13 |
14 | export const deleteEnrollmentKey = createAsyncAction(
15 | 'EnrollmentKeys_deleteEnrollmentKey_Request',
16 | 'EnrollmentKeys_deleteEnrollmentKey_Success',
17 | 'EnrollmentKeys_deleteEnrollmentKey_Failure'
18 | )()
19 |
20 | export const createEnrollmentKey = createAsyncAction(
21 | 'EnrollmentKeys_createEnrollmentKey_Request',
22 | 'EnrollmentKeys_createEnrollmentKey_Success',
23 | 'EnrollmentKeys_createEnrollmentKey_Failure'
24 | )()
25 |
26 | export const clearEnrollmentKeys = createAction('EnrollmentKeys_clearEnrollmentKeys')()
27 |
--------------------------------------------------------------------------------
/src/store/modules/enrollmentkeys/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/enrollmentkeys/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getEnrollmentKeysState = (state: RootState) => state.enrollmentKeys
5 |
6 | export const isProcessingEnrollmentKeys = createSelector(
7 | getEnrollmentKeysState,
8 | (hostsState) => hostsState.isProcessing
9 | )
10 |
11 | export const getEnrollmentKeys = createSelector(
12 | getEnrollmentKeysState,
13 | (hostsState) => hostsState.enrollmentKeys
14 | )
15 |
--------------------------------------------------------------------------------
/src/store/modules/enrollmentkeys/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * EnrollmentKey is a key used to control netclient access to netmaker sever.
3 | */
4 | export interface EnrollmentKey {
5 | value: string // ID
6 | tags: string[] // names
7 | token: string
8 | networks: string[]
9 | expiration: number
10 | uses_remaining: number
11 | unlimited: boolean
12 | }
13 |
14 | export interface GetEnrollmentKeysPayload {
15 | Request: void
16 | Response: EnrollmentKey[]
17 | }
18 |
19 | export interface DeleteEnrollmentKeyPayload {
20 | Request: {
21 | id: EnrollmentKey['value']
22 | }
23 | Response: { id: EnrollmentKey['value'] }
24 | }
25 |
26 | export interface CreateEnrollmentKeyPayload {
27 | Request: Omit
28 | Response: EnrollmentKey
29 | }
30 |
--------------------------------------------------------------------------------
/src/store/modules/hosts/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createAsyncAction } from 'typesafe-actions'
2 | import {
3 | GetHostsPayload,
4 | UpdateHostPayload,
5 | DeleteHostPayload,
6 | UpdateHostNetworksPayload,
7 | DeleteHostRelayPayload,
8 | CreateHostRelayPayload,
9 | RefreshHostKeysPayload,
10 | } from '.'
11 |
12 | export const getHosts = createAsyncAction(
13 | 'Hosts_getHosts_Request',
14 | 'Hosts_getHosts_Success',
15 | 'Hosts_getHosts_Failure'
16 | )()
17 |
18 | export const updateHost = createAsyncAction(
19 | 'Hosts_updateHosts_Request',
20 | 'Hosts_updateHosts_Success',
21 | 'Hosts_updateHosts_Failure'
22 | )()
23 |
24 | export const updateHostNetworks = createAsyncAction(
25 | 'Hosts_updateHostNetworks_Request',
26 | 'Hosts_updateHostNetworks_Success',
27 | 'Hosts_updateHostNetworks_Failure'
28 | )<
29 | UpdateHostNetworksPayload['Request'],
30 | UpdateHostNetworksPayload['Response'],
31 | Error
32 | >()
33 |
34 | export const refreshHostKeys = createAsyncAction(
35 | 'Hosts_refreshHostKeys_Request',
36 | 'Hosts_refreshHostKeys_Success',
37 | 'Hosts_refreshHostKeys_Failure'
38 | )<
39 | RefreshHostKeysPayload['Request'],
40 | RefreshHostKeysPayload['Response'],
41 | Error
42 | >()
43 |
44 | export const deleteHost = createAsyncAction(
45 | 'Hosts_deleteHosts_Request',
46 | 'Hosts_deleteHosts_Success',
47 | 'Hosts_deleteHosts_Failure'
48 | )()
49 |
50 | export const createHostRelay = createAsyncAction(
51 | 'Hosts_createHostRelay_Request',
52 | 'Hosts_createHostRelay_Success',
53 | 'Hosts_createHostRelay_Failure'
54 | )<
55 | CreateHostRelayPayload['Request'],
56 | CreateHostRelayPayload['Response'],
57 | Error
58 | >()
59 |
60 | export const deleteHostRelay = createAsyncAction(
61 | 'Hosts_deleteHostRelay_Request',
62 | 'Hosts_deleteHostRelay_Success',
63 | 'Hosts_deleteHostRelay_Failure'
64 | )<
65 | DeleteHostRelayPayload['Request'],
66 | DeleteHostRelayPayload['Response'],
67 | Error
68 | >()
69 |
70 | export const clearHosts = createAction('Hosts_clearHosts')()
71 |
--------------------------------------------------------------------------------
/src/store/modules/hosts/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/hosts/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getHostsState = (state: RootState) => state.hosts
5 |
6 | export const isProcessingHosts = createSelector(
7 | getHostsState,
8 | (hostsState) => hostsState.isProcessing
9 | )
10 |
11 | export const getHosts = createSelector(
12 | getHostsState,
13 | (hostsState) => hostsState.hosts
14 | )
15 |
16 | export const getHostsMap = createSelector(
17 | getHostsState,
18 | (hostsState) => hostsState.hostsMap
19 | )
20 |
--------------------------------------------------------------------------------
/src/store/modules/hosts/types.ts:
--------------------------------------------------------------------------------
1 | import { Interface, Node } from '../node/types'
2 |
3 | /**
4 | * Host is the configuration for a netclient host machine.
5 | * unlike the Node type, a host is independent of a network
6 | * a host may have different representations on different networks as Nodes
7 | */
8 | export interface Host {
9 | id: string
10 | verbosity: number
11 | firewallinuse: string
12 | version: string
13 | name: string
14 | os: string
15 | debug: boolean
16 | isstatic: boolean
17 | listenport: number
18 | locallistenport: number
19 | proxy_listen_port: number
20 | mtu: number
21 | interfaces: Interface[]
22 | defaultinterface: string // iface name
23 | endpointip: string
24 | publickey: string
25 | macaddress: string
26 | internetgateway: string
27 | nodes: string[] // node ids
28 | proxy_enabled: boolean
29 | isdefault: boolean
30 | isrelayed: boolean
31 | relayed_by: string // host id
32 | isrelay: boolean
33 | relay_hosts: string[] // host ids
34 | }
35 |
36 | export interface NodeCommonDetails {
37 | name: Host['name']
38 | version: Host['version']
39 | endpointip: Host['endpointip']
40 | publickey: Host['publickey']
41 | os: Host['os']
42 | listenport: Host['listenport']
43 | isstatic: Host['isstatic']
44 | mtu: Host['mtu']
45 | interfaces: Host['interfaces']
46 | isrelay: Host['isrelay']
47 | relay_hosts: Host['relay_hosts']
48 | isrelayed: Host['isrelayed']
49 | macaddress: Host['macaddress']
50 | }
51 |
52 | export interface GetHostsPayload {
53 | Request: void
54 | Response: Host[]
55 | }
56 |
57 | export interface UpdateHostPayload {
58 | Request: Host
59 | Response: Host
60 | }
61 |
62 | export interface RefreshHostKeysPayload {
63 | Request: { id: Host['id'] }
64 | Response: void
65 | }
66 |
67 | export interface UpdateHostNetworksPayload {
68 | Request: {
69 | id: Host['id']
70 | network: Node['network']
71 | action: 'join' | 'leave'
72 | }
73 | Response: void
74 | }
75 |
76 | export interface DeleteHostPayload {
77 | Request: {
78 | hostid: string
79 | }
80 | Response: Host
81 | }
82 |
83 | export interface CreateHostRelayPayload {
84 | Request: {
85 | hostid: Host['id']
86 | relayed_hosts: Host['id'][]
87 | }
88 | Response: Host
89 | }
90 |
91 | export interface DeleteHostRelayPayload {
92 | Request: {
93 | hostid: Host['id']
94 | }
95 | Response: Host
96 | }
97 |
--------------------------------------------------------------------------------
/src/store/modules/network/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/network/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getNetwork = (state: RootState) => state.network
5 | export const isFetchingNetworks = createSelector(
6 | getNetwork,
7 | (network) => network.isFetching
8 | )
9 | export const getNetworks = createSelector(
10 | getNetwork,
11 | (network) => network.networks
12 | )
13 |
14 | export const getDnsEntries = createSelector(
15 | getNetwork,
16 | (network) => network.dnsEntries
17 | )
18 |
19 | export const getTempKey = createSelector(
20 | getNetwork,
21 | (network) => network.tempkey
22 | )
23 |
--------------------------------------------------------------------------------
/src/store/modules/network/utils.ts:
--------------------------------------------------------------------------------
1 | import { Network, NetworkPayload } from './types'
2 |
3 | export const networkToNetworkPayload = (network: Network): NetworkPayload => {
4 |
5 | if (!!network.prosettings) {
6 | if (typeof network.prosettings.allowedgroups === 'string') {
7 | network.prosettings.allowedgroups = convertStringToArray(network.prosettings.allowedgroups)
8 | }
9 | if (typeof network.prosettings.allowedusers === 'string') {
10 | network.prosettings.allowedusers = convertStringToArray(network.prosettings.allowedusers)
11 | }
12 | }
13 |
14 | return {
15 | ...network,
16 | defaultmtu: Number(network.defaultmtu),
17 | defaultlistenport: Number(network.defaultlistenport),
18 | defaultkeepalive: Number(network.defaultkeepalive),
19 | isipv4: network.isipv4 ? 'yes' : 'no',
20 | isipv6: network.isipv6 ? 'yes' : 'no',
21 | defaultudpholepunch: network.defaultudpholepunch ? 'yes' : 'no',
22 | defaultacl: network.defaultacl ? 'yes' : 'no',
23 | prosettings: !!network.prosettings ? {
24 | defaultaccesslevel: Number(network.prosettings.defaultaccesslevel),
25 | defaultuserclientlimit: Number(network.prosettings.defaultuserclientlimit),
26 | defaultusernodelimit: Number(network.prosettings.defaultusernodelimit),
27 | allowedgroups: network.prosettings.allowedgroups,
28 | allowedusers: network.prosettings.allowedusers,
29 | } : undefined
30 | }
31 | }
32 | export const networkPayloadToNetwork = (network: NetworkPayload): Network => {
33 | return {
34 | ...network,
35 | defaultmtu: Number(network.defaultmtu),
36 | defaultlistenport: Number(network.defaultlistenport),
37 | defaultkeepalive: Number(network.defaultkeepalive),
38 | isipv4: network.isipv4 === 'yes',
39 | isipv6: network.isipv6 === 'yes',
40 | defaultudpholepunch: network.defaultudpholepunch === 'yes',
41 | defaultacl: network.defaultacl === 'yes',
42 | prosettings: !!network.prosettings ? {
43 | defaultaccesslevel: Number(network.prosettings.defaultaccesslevel),
44 | defaultuserclientlimit: Number(network.prosettings.defaultuserclientlimit),
45 | defaultusernodelimit: Number(network.prosettings.defaultusernodelimit),
46 | allowedgroups: network.prosettings.allowedgroups,
47 | allowedusers: network.prosettings.allowedusers,
48 | } : undefined
49 | }
50 | }
51 |
52 | const convertStringToArray = (commaSeparatedData: string) => {
53 | const data = commaSeparatedData.split(',')
54 | for (let i = 0; i < data.length; i++) {
55 | data[i] = data[i].trim()
56 | }
57 | return data
58 | }
59 |
--------------------------------------------------------------------------------
/src/store/modules/node/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/node/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getNodeState = (state: RootState) => state.node
5 |
6 | export const isFetchingNodes = createSelector(
7 | getNodeState,
8 | (node) => node.isFetching
9 | )
10 | export const getNodes = createSelector(getNodeState, (node) => node.nodes)
11 |
12 | export const getExtClients = createSelector(
13 | getNodeState,
14 | (node) => node.externalClients
15 | )
16 |
17 | export const getCurrentQrCode = createSelector(
18 | getNodeState,
19 | (node) => node.qrData
20 | )
21 |
22 | export const getShouldSignOut = createSelector(
23 | getNodeState,
24 | (node) => node.shouldSignOut
25 | )
26 |
27 | export const getNodeSort = createSelector(getNodeState, (node) => node.nodeSort)
28 |
29 | export const getNodesMap = createSelector(getNodeState, (node) => node.nodesMap)
30 |
--------------------------------------------------------------------------------
/src/store/modules/node/utils.ts:
--------------------------------------------------------------------------------
1 | export function download(filename: string, text: string) {
2 | var element = document.createElement('a')
3 | element.setAttribute(
4 | 'href',
5 | 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)
6 | )
7 | element.setAttribute('download', filename)
8 |
9 | element.style.display = 'none'
10 | document.body.appendChild(element)
11 |
12 | element.click()
13 |
14 | document.body.removeChild(element)
15 | }
16 |
--------------------------------------------------------------------------------
/src/store/modules/pro/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createAsyncAction } from 'typesafe-actions'
2 | import { UserGroupsPayload, UserGroupDelorCreate } from '.'
3 | import {
4 | NetworkUsersPayload,
5 | NetworkUserDelPayload,
6 | NetworkUserUpdatePayload,
7 | NetworkUserGetPayload,
8 | ProCreateAccessKeyPayload,
9 | } from '.'
10 | import { TempKey } from '../network'
11 |
12 | export const getUserGroups = createAsyncAction(
13 | 'Pro_getUserGroups',
14 | 'Pro_getUserGroups_Success',
15 | 'Pro_getUserGroups_Failure'
16 | )()
17 |
18 | export const createUserGroup = createAsyncAction(
19 | 'Pro_createUserGroup_Request',
20 | 'Pro_createUserGroup_Success',
21 | 'Pro_createUserGroup_Failure'
22 | )()
23 |
24 | export const deleteUserGroup = createAsyncAction(
25 | 'Pro_deleteUserGroup_Request',
26 | 'Pro_deleteUserGroup_Success',
27 | 'Pro_deleteUserGroup_Failure'
28 | )()
29 |
30 | export const getNetworkUsers = createAsyncAction(
31 | 'Pro_getNetworkUsers_Request',
32 | 'Pro_getNetworkUsers_Success',
33 | 'Pro_getNetworkUsers_Failure'
34 | )()
35 |
36 | export const deleteNetworkUser = createAsyncAction(
37 | 'Pro_deleteNetworkUser_Request',
38 | 'Pro_deleteNetworkUser_Success',
39 | 'Pro_deleteNetworkUser_Failure'
40 | )()
41 |
42 | export const updateNetworkUser = createAsyncAction(
43 | 'Pro_updateNetworkUser_Request',
44 | 'Pro_updateNetworkUser_Success',
45 | 'Pro_updateNetworkUser_Failure'
46 | )<
47 | NetworkUserUpdatePayload['Request'],
48 | NetworkUserUpdatePayload['Response'],
49 | Error
50 | >()
51 |
52 | export const getNetworkUserData = createAsyncAction(
53 | 'Pro_getNetworkUserData_Request',
54 | 'Pro_getNetworkUserData_Success',
55 | 'Pro_getNetworkUserData_Failure'
56 | )<
57 | NetworkUserGetPayload['Request'],
58 | NetworkUserGetPayload['Response'],
59 | Error
60 | >()
61 |
62 | export const proCreateAccessKey = createAsyncAction(
63 | 'Pro_proCreateAccessKey_Request',
64 | 'Pro_proCreateAccessKey_Success',
65 | 'Pro_proCreateAccessKey_Failure'
66 | )<
67 | ProCreateAccessKeyPayload['Request'],
68 | ProCreateAccessKeyPayload['Response'],
69 | Error
70 | >()
71 |
72 | export const clearCurrentAccessKey = createAction('clearCurrentAccessKey')()
73 | export const setCurrentAccessKey = createAction('setCurrentAccessKey')()
74 |
--------------------------------------------------------------------------------
/src/store/modules/pro/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/pro/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getProState = (state: RootState) => state.pro
5 |
6 | export const isProcessing = createSelector(
7 | getProState,
8 | (pro) => pro.isProcessing
9 | )
10 |
11 | export const userGroups = createSelector(getProState, (pro) => pro.userGroups)
12 |
13 | export const networkUsers = createSelector(
14 | getProState,
15 | (pro) => pro.networkUsers
16 | )
17 |
18 | export const networkUserData = createSelector(
19 | getProState,
20 | (pro) => pro.networkUserData
21 | )
22 |
23 | export const getNetworkUserNetworks = createSelector(getProState, (pro) =>
24 | Object.keys(pro.networkUserData)
25 | )
26 |
27 | export const getCurrentAccessKey = createSelector(getProState, (pro) => pro.currentAccessKey)
28 |
29 | export const isCreatingGroup = createSelector(getProState, (pro) => pro.isCreatingGroup)
30 |
--------------------------------------------------------------------------------
/src/store/modules/pro/types.ts:
--------------------------------------------------------------------------------
1 | import { MutableRequired } from '../acls'
2 | import { Node, ExternalClient, Network, NetworkPayload, AccessKey } from '../../types'
3 |
4 | export type UserGroups = MutableRequired<{ [groupName: string]: void }>
5 |
6 | export interface UserGroupsPayload {
7 | Request: void
8 | Response: UserGroups
9 | }
10 |
11 | export interface UserGroupDelorCreate {
12 | Request: { groupName: string }
13 | Response: { groupName: string }
14 | }
15 |
16 | export interface NetworkUser {
17 | accesslevel: number
18 | clientlimit: number
19 | nodelimit: number
20 | id: string
21 | clients: Array
22 | nodes: Array
23 | }
24 |
25 | export interface NetworkUserData {
26 | nodes: Array
27 | clients: Array
28 | vpns: Array
29 | networks: Array
30 | user: NetworkUser
31 | }
32 |
33 | export interface NetworkUserDataPayload {
34 | nodes: Array
35 | clients: Array
36 | vpns: Array
37 | networks: Array
38 | user: NetworkUser
39 | }
40 |
41 | export type NetworksUsersMap = MutableRequired<{
42 | [networkName: string]: NetworkUser[]
43 | }>
44 |
45 | export type NetworkUserDataMap = MutableRequired<{
46 | [networkName: string]: NetworkUserData
47 | }>
48 |
49 | export type NetworkUserDataMapPayload = MutableRequired<{
50 | [networkName: string]: NetworkUserDataPayload
51 | }>
52 |
53 | export interface NetworkUsersPayload {
54 | Request: void
55 | Response: NetworksUsersMap
56 | }
57 |
58 | export interface NetworkUserDelPayload {
59 | Request: { networkName: string; networkUserID: string }
60 | Response: { networkName: string; networkUserID: string }
61 | }
62 |
63 | export interface NetworkUserUpdatePayload {
64 | Request: { networkName: string; networkUser: NetworkUser }
65 | Response: { networkName: string; networkUser: NetworkUser }
66 | }
67 |
68 | export interface NetworkUserGetPayload {
69 | Request: { networkUserID: string }
70 | Response: NetworkUserDataMapPayload
71 | }
72 |
73 | export interface ProCreateAccessKeyPayload {
74 | Request: {
75 | netid: string
76 | newAccessKey: {
77 | name: string
78 | uses: number
79 | }
80 | }
81 | Response: {
82 | netid: string
83 | newAccessKey: AccessKey
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/store/modules/pro/utils.ts:
--------------------------------------------------------------------------------
1 | import { networkPayloadToNetwork } from "../network/utils";
2 | import { NetworkUserData, NetworkUserDataMap, NetworkUserDataMapPayload } from "./types";
3 |
4 | export function netUserDataPayloadToNetUserData(payload: NetworkUserDataMapPayload): NetworkUserDataMap {
5 | let newMap = {} as NetworkUserDataMap
6 |
7 | if (!!payload) {
8 | Object.keys(payload).map(network => {
9 | newMap[network] = {} as NetworkUserData
10 | if (payload[network]) {
11 | if (payload[network].nodes)
12 | newMap[network].nodes = payload[network].nodes
13 | if (payload[network].vpns)
14 | newMap[network].vpns = payload[network].vpns
15 | if (payload[network].networks)
16 | newMap[network].networks = payload[network].networks.map(n => networkPayloadToNetwork(n))
17 | if (payload[network].clients)
18 | newMap[network].clients = payload[network].clients
19 | if (payload[network].user)
20 | newMap[network].user = payload[network].user
21 | }
22 | return null
23 | })
24 | }
25 |
26 | return newMap
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/modules/router/Component.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import { useHistory } from 'react-router-dom'
4 | import { LocationListener } from 'history'
5 | import { routerActions } from './actions'
6 |
7 | export const RouterState = () => {
8 | const history = useHistory()
9 | const dispatch = useDispatch()
10 |
11 | const historyListener = useCallback(
12 | (location, action) => {
13 | dispatch(
14 | routerActions[action]({
15 | location,
16 | history,
17 | })
18 | )
19 | },
20 | [dispatch, history]
21 | )
22 |
23 | useEffect(() => {
24 | historyListener(history.location, 'POP')
25 | return history.listen(historyListener)
26 | }, [history, historyListener])
27 |
28 | return null
29 | }
30 |
--------------------------------------------------------------------------------
/src/store/modules/router/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from 'typesafe-actions'
2 | import { RouterAction } from './types'
3 |
4 | const RouterActionPrefix = `router`
5 |
6 | export const routerPush = createAction(
7 | `${RouterActionPrefix}/PUSH`
8 | )()
9 |
10 | export const routerPop = createAction(
11 | `${RouterActionPrefix}/POP`
12 | )()
13 |
14 | export const routerReplace = createAction(
15 | `${RouterActionPrefix}/REPLACE`
16 | )()
17 |
18 | export const routerActions = {
19 | POP: routerPop,
20 | PUSH: routerPush,
21 | REPLACE: routerReplace,
22 | }
23 |
--------------------------------------------------------------------------------
/src/store/modules/router/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/router/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from 'typesafe-actions'
2 | import { routerPop, routerPush, routerReplace } from './actions'
3 | import { RouterState } from './types'
4 |
5 | export const reducer = createReducer({}).handleAction(
6 | [routerPush, routerReplace, routerPop],
7 | (state, action) => ({
8 | prevLocation: state.location,
9 | history: action.payload.history,
10 | location: action.payload.location,
11 | })
12 | )
13 |
--------------------------------------------------------------------------------
/src/store/modules/router/saga.ts:
--------------------------------------------------------------------------------
1 | import { all } from 'redux-saga/effects'
2 |
3 | export function* saga() {
4 | yield all([])
5 | }
6 |
--------------------------------------------------------------------------------
/src/store/modules/router/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getRouter = (state: RootState) => state.router
5 |
6 | export const getLocation = createSelector(getRouter, (state) => state.location)
7 |
8 | export const getHistory = createSelector(getRouter, (state) => state.history)
9 |
--------------------------------------------------------------------------------
/src/store/modules/router/types.ts:
--------------------------------------------------------------------------------
1 | import { History, Location } from 'history'
2 |
3 | export interface RouterState {
4 | history?: History
5 | location?: Location
6 | prevLocation?: Location
7 | }
8 |
9 | export interface RouterAction {
10 | history: History
11 | location: Location
12 | }
13 |
--------------------------------------------------------------------------------
/src/store/modules/server/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createAsyncAction } from 'typesafe-actions'
2 | import {
3 | GetServerConfigPayload,
4 | LogsPayload,
5 | Metrics,
6 | ExtMetricsPayload,
7 | AllNodesMetricsPayload,
8 | NetworkMetricsPayload,
9 | } from './types'
10 |
11 | export const getServerConfig = createAsyncAction(
12 | 'api_getServerConfig_Request',
13 | 'api_getServerConfig_Success',
14 | 'api_getServerConfig_Failure'
15 | )<
16 | GetServerConfigPayload['Request'],
17 | GetServerConfigPayload['Response'],
18 | Error
19 | >()
20 |
21 | export const getServerLogs = createAsyncAction(
22 | 'api_getServerLogs_Request',
23 | 'api_getServerLogs_Success',
24 | 'api_getServerLogs_Failure'
25 | )()
26 |
27 | export const getNodeMetrics = createAsyncAction(
28 | 'api_getNodeMetrics_Request',
29 | 'api_getNodeMetrics_Success',
30 | 'api_getNodeMetrics_Failure'
31 | )<
32 | AllNodesMetricsPayload['Request'],
33 | AllNodesMetricsPayload['Response'],
34 | Error
35 | >()
36 |
37 | export const getMetrics = createAsyncAction(
38 | 'api_getMetrics_Request',
39 | 'api_getMetrics_Success',
40 | 'api_getMetrics_Failure'
41 | )()
42 |
43 | export const getNetworkMetrics = createAsyncAction(
44 | 'api_getNetworkMetrics_Request',
45 | 'api_getNetworkMetrics_Success',
46 | 'api_getNetworkMetrics_Failure'
47 | )()
48 |
49 | export const getExtMetrics = createAsyncAction(
50 | 'api_getExtMetrics_Request',
51 | 'api_getExtMetrics_Success',
52 | 'api_getExtMetrics_Failure'
53 | )()
54 |
55 | export const clearCurrentMetrics = createAction('clearCurrentMetrics')()
56 |
--------------------------------------------------------------------------------
/src/store/modules/server/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { reducer } from './reducer'
3 | export { saga } from './saga'
4 | export * as selectors from './selectors'
5 | export * from './types'
6 |
--------------------------------------------------------------------------------
/src/store/modules/server/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { RootState } from '../../reducers'
3 |
4 | const getServer = (state: RootState) => state.server
5 | export const getServerConfig = createSelector(
6 | getServer,
7 | (server) => server.config
8 | )
9 | export const isFetchingServerConfig = createSelector(
10 | getServer,
11 | (server) => server.isFetching
12 | )
13 | export const getServerLogs = createSelector(getServer, (server) => server.logs)
14 | export const getNodeMetrics = createSelector(
15 | getServer,
16 | (server) => server.nodeMetrics
17 | )
18 | export const getAttempts = createSelector(
19 | getServer,
20 | (server) => server.attempts
21 | )
22 | export const hasFetchedNodeMetrics = createSelector(
23 | getServer,
24 | (server) => server.fetchedNodeMetrics
25 | )
26 |
27 | export const getNetworkExtMetrics = (network: string) =>
28 | createSelector(getServer, (server) => server.extMetrics[network])
29 |
30 | export const getNodeMetric = (nodeid: string) =>
31 | createSelector(getNodeMetrics, (nodeMetrics) => nodeMetrics[nodeid])
32 |
33 | export const getNetworkMetrics = (network: string) =>
34 | createSelector(getServer, (server) => server.networkMetrics[network])
35 |
36 | export const isFetchingClientMetrics = createSelector(
37 | getServer,
38 | (server) => server.fetchingExtMetrics
39 | )
40 |
--------------------------------------------------------------------------------
/src/store/modules/toast/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from 'typesafe-actions'
2 | import { ToastPayload } from './types'
3 |
4 | export const toast = createAction('toast_toast')()
5 |
--------------------------------------------------------------------------------
/src/store/modules/toast/index.ts:
--------------------------------------------------------------------------------
1 | export * as actions from './actions'
2 | export { saga } from './saga'
3 | export * from './types'
4 |
--------------------------------------------------------------------------------
/src/store/modules/toast/types.ts:
--------------------------------------------------------------------------------
1 | import { ToastContent, ToastOptions, UpdateOptions } from 'react-toastify'
2 | import { PayloadActionCreator } from 'typesafe-actions'
3 |
4 | export type ToastPromiseType = string | UpdateOptions
5 | export type ToastPromiseFunctionType = (value: T) => ToastPromiseType
6 | export interface ToastPromiseParams {
7 | pending: ToastPromiseType
8 | success: ToastPromiseType | ToastPromiseFunctionType
9 | error: ToastPromiseType | ToastPromiseFunctionType
10 | }
11 |
12 | export interface ToastPayload {
13 | method: 'info' | 'warn' | 'success' | 'error'
14 | content: ToastContent
15 | options?: ToastOptions
16 | }
17 |
18 | export interface AsyncToastPayload {
19 | params: ToastPromiseParams
20 | promise: {
21 | method: (...args: any[]) => Promise
22 | params: any[]
23 | }
24 | options?: ToastOptions
25 | }
26 |
27 | export interface GeneratorToastPayload {
28 | params: ToastPromiseParams
29 | success: PayloadActionCreator
30 | error: PayloadActionCreator
31 | options?: ToastOptions
32 | }
33 |
--------------------------------------------------------------------------------
/src/store/reducers.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import { StateType } from 'typesafe-actions'
3 |
4 | import { reducer as auth } from './modules/auth'
5 | import { reducer as api } from './modules/api'
6 | import { reducer as network } from './modules/network'
7 | import { reducer as node } from './modules/node'
8 | import { reducer as server } from './modules/server'
9 | import { reducer as router } from './modules/router'
10 | import { reducer as acls } from './modules/acls'
11 | import { reducer as pro } from './modules/pro'
12 | import { reducer as hosts } from './modules/hosts'
13 | import { reducer as enrollmentKeys } from './modules/enrollmentkeys'
14 |
15 | export const createRootReducer = () =>
16 | combineReducers({
17 | auth,
18 | api,
19 | network,
20 | node,
21 | server,
22 | router,
23 | acls,
24 | pro,
25 | hosts,
26 | enrollmentKeys,
27 | })
28 |
29 | export type RootState = StateType>
30 |
--------------------------------------------------------------------------------
/src/store/sagas.ts:
--------------------------------------------------------------------------------
1 | import { all } from 'redux-saga/effects'
2 |
3 | import { saga as auth } from './modules/auth'
4 | import { saga as api } from './modules/api'
5 | import { saga as network } from './modules/network'
6 | import { saga as node } from './modules/node'
7 | import { saga as server } from './modules/server'
8 | import { saga as toast } from './modules/toast'
9 | import { saga as router } from './modules/router'
10 | import { saga as acls } from './modules/acls'
11 | import { saga as pro } from './modules/pro'
12 | import { saga as hosts } from './modules/hosts'
13 | import { saga as enrollmentKeys } from './modules/enrollmentkeys'
14 |
15 | export const createRootSaga = () => {
16 | function* rootSaga() {
17 | yield all([
18 | auth(),
19 | api(),
20 | network(),
21 | node(),
22 | server(),
23 | toast(),
24 | router(),
25 | acls(),
26 | pro(),
27 | hosts(),
28 | enrollmentKeys(),
29 | ])
30 | }
31 | return rootSaga
32 | }
33 |
--------------------------------------------------------------------------------
/src/store/selectors.ts:
--------------------------------------------------------------------------------
1 | export { selectors as authSelectors } from './modules/auth'
2 | export { selectors as apiSelectors } from './modules/api'
3 | export { selectors as networkSelectors } from './modules/network'
4 | export { selectors as nodeSelectors } from './modules/node'
5 | export { selectors as serverSelectors } from './modules/server'
6 | export { selectors as toastSelectors } from './modules/server'
7 | export { selectors as routerSelectors } from './modules/router'
8 | export { selectors as aclSelectors } from './modules/acls'
9 | export { selectors as proSelectors } from './modules/pro'
10 | export { selectors as hostsSelectors } from './modules/hosts'
11 | export { selectors as enrollmentKeysSelectors } from './modules/enrollmentkeys'
12 |
--------------------------------------------------------------------------------
/src/store/types.ts:
--------------------------------------------------------------------------------
1 | export type { RootState } from './reducers'
2 | export * from './selectors'
3 | export * from './actions'
4 |
5 | export * from './modules/auth/types'
6 | export * from './modules/network/types'
7 | export * from './modules/node/types'
8 | export * from './modules/server/types'
9 | export * from './modules/api/types'
10 | export * from './modules/toast/types'
11 | export * from './modules/router/types'
12 | export * from './modules/acls/types'
13 | export * from './modules/pro/types'
14 | export * from './modules/hosts/types'
15 | export * from './modules/enrollmentkeys/types'
16 |
--------------------------------------------------------------------------------
/src/types/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare type DeepPartial = {
4 | [P in keyof T]?: T[P] extends Array
5 | ? Array>
6 | : T[P] extends ReadonlyArray
7 | ? ReadonlyArray>
8 | : DeepPartial
9 | }
10 |
11 | export declare type Modify = Omit & R
12 |
13 | export { Theme, ThemeOptions } from '@mui/material/styles'
14 | declare module '@mui/material/styles' {
15 | interface Theme {
16 | status: {
17 | danger: string
18 | }
19 | }
20 | // allow configuration using `createTheme`
21 | interface ThemeOptions extends DeepPartial {}
22 | }
23 |
24 | declare module '@mui/styles' {
25 | import { Theme, ThemeOptions } from '@mui/material/styles'
26 | interface DefaultTheme extends Theme {}
27 | }
28 |
29 | declare module 'react' {
30 | function forwardRef(
31 | render: (props: P, ref: React.Ref) => React.ReactElement | null
32 | ): (props: P & React.RefAttributes) => React.ReactElement | null
33 | }
34 |
--------------------------------------------------------------------------------
/src/util/enrollmentkeys.ts:
--------------------------------------------------------------------------------
1 | import { EnrollmentKey } from '~store/modules/enrollmentkeys'
2 |
3 | export function isEnrollmentKeyValid(key: EnrollmentKey): boolean {
4 | if (key === undefined || key === null) {
5 | return false
6 | }
7 | if (key.uses_remaining > 0) {
8 | return true
9 | }
10 | if (new Date(key.expiration).getTime() > Date.now()) {
11 | return true
12 | }
13 |
14 | return key.unlimited
15 | }
16 |
--------------------------------------------------------------------------------
/src/util/errorpage.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Grid } from '@mui/material'
2 | import Typography from '@mui/material/Typography'
3 | import { useTranslation } from 'react-i18next'
4 | import Logo from '../netmaker-logo.png'
5 | import DarkLogo from '../netmaker-logo-2.png'
6 | import { useSelector } from 'react-redux'
7 | import { authSelectors } from '~store/selectors'
8 | import WarningIcon from '@mui/icons-material/Warning'
9 | import { Link } from 'react-router-dom'
10 |
11 | export const NotFound: React.FC = () => {
12 | const { t } = useTranslation()
13 | const inDarkMode = useSelector(authSelectors.isInDarkMode)
14 |
15 | const styles = {
16 | logo: {
17 | objectFit: 'cover',
18 | // height: 70,
19 | maxWidth: '40%',
20 | height: 'auto',
21 | width: 'auto',
22 | minWidth: '8em',
23 | } as React.DetailedHTMLProps<
24 | React.ImgHTMLAttributes,
25 | HTMLImageElement
26 | >,
27 | center: {
28 | textAlign: 'center'
29 | } as React.DetailedHTMLProps<
30 | React.ImgHTMLAttributes,
31 | HTMLImageElement
32 | >,
33 | }
34 |
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 |
50 | {`${t('error.notfound')}`}
51 |
52 |
53 |
54 |
61 |
62 |
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/util/genericerror.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from '@mui/material'
2 | import Typography from '@mui/material/Typography'
3 | import { useTranslation } from 'react-i18next'
4 | import Logo from '../netmaker-logo.png'
5 | import DarkLogo from '../netmaker-logo-2.png'
6 | import { useSelector } from 'react-redux'
7 | import { authSelectors } from '~store/selectors'
8 | import WarningIcon from '@mui/icons-material/Report'
9 |
10 | export const GenericError: React.FC<{message?: string}> = ({message}) => {
11 | const { t } = useTranslation()
12 | const inDarkMode = useSelector(authSelectors.isInDarkMode)
13 |
14 | const styles = {
15 | logo: {
16 | objectFit: 'cover',
17 | // height: 70,
18 | maxWidth: '40%',
19 | height: 'auto',
20 | width: 'auto',
21 | minWidth: '8em',
22 | } as React.DetailedHTMLProps<
23 | React.ImgHTMLAttributes,
24 | HTMLImageElement
25 | >,
26 | center: {
27 | textAlign: 'center'
28 | } as React.DetailedHTMLProps<
29 | React.ImgHTMLAttributes,
30 | HTMLImageElement
31 | >,
32 | }
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
49 | {`${message || t('error.noresults')}`}
50 |
51 |
52 |
53 |
58 |
59 |
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/src/util/hosts.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { createSelector } from 'reselect'
4 | import { RootState } from 'typesafe-actions'
5 | import { Host } from '~store/modules/hosts'
6 | import { hostsSelectors } from '~store/selectors'
7 |
8 | const hostByIdPredicate = (id: Host['id']) => (host: Host) => host.id === id
9 |
10 | const makeSelectHostByID = () =>
11 | createSelector(
12 | hostsSelectors.getHosts,
13 | (_: RootState, id: Host['id']) => id,
14 | (hosts, id) => hosts.find(hostByIdPredicate(id))
15 | )
16 |
17 | export const useGetHostById = (id: Host['id']) => {
18 | const selectHost = useMemo(makeSelectHostByID, [])
19 | return useSelector((state) =>
20 | selectHost(state, id)
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/util/network.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import { createSelector } from 'reselect'
4 | import { RootState } from 'typesafe-actions'
5 | import { Network } from '~modules/network/types'
6 | import { Node } from '~modules/node/types'
7 | import { networkSelectors, nodeSelectors } from '~store/selectors'
8 |
9 | const networkByNetIdPredicate = (netid: string) => (network: Network) =>
10 | network.netid === netid
11 |
12 | const makeSelectNetwork = () =>
13 | createSelector(
14 | networkSelectors.getNetworks,
15 | (_: RootState, netid: string) => netid,
16 | (networks, netid) => networks.find(networkByNetIdPredicate(netid))
17 | )
18 |
19 | export const useNetwork = (netid: string) => {
20 | const selectNetwork = useMemo(makeSelectNetwork, [])
21 | return useSelector((state) =>
22 | selectNetwork(state, netid)
23 | )
24 | }
25 |
26 | const nodesByNetworkIdPredicate = (id: Network['netid']) => (node: Node) =>
27 | node.network === id
28 |
29 | const makeSelectNodeByNetworkId = () =>
30 | createSelector(
31 | nodeSelectors.getNodes,
32 | (_: RootState, id: Network['netid']) => id,
33 | (nodes, id) => nodes.filter(nodesByNetworkIdPredicate(id))
34 | )
35 |
36 | export const useNodesByNetworkId = (id: Network['netid']) : Array | undefined => {
37 | const selectNodes = useMemo(makeSelectNodeByNetworkId, [])
38 | return useSelector | undefined>((state) =>
39 | selectNodes(state, id)
40 | )
41 | }
42 |
43 | export const useNodeCountByNetworkId = (id: Network['netid']) => {
44 | const nodes = useNodesByNetworkId(id)
45 | if (nodes) {
46 | return nodes.length
47 | }
48 | return 0
49 | }
50 |
51 | // Convert byte array to string.
52 | // Useful for converting IP addresses from backend to readable string
53 | export function byteArrayToString(byteArray: any): string {
54 | return btoa(String.fromCharCode(...new Uint8Array(byteArray)));
55 | }
56 |
--------------------------------------------------------------------------------
/src/util/query.ts:
--------------------------------------------------------------------------------
1 | import { useLocation } from 'react-router-dom'
2 |
3 | export function useQuery() {
4 | return new URLSearchParams(useLocation().search)
5 | }
6 |
--------------------------------------------------------------------------------
/src/util/regex.ts:
--------------------------------------------------------------------------------
1 | export const correctUserNameRegex = new RegExp(
2 | /^([a-zA-Z0-9,\-,.]{3,40})|([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4})$/i
3 | )
4 |
5 | export const correctPasswordRegex = new RegExp(/^.{5,64}$/i)
6 |
7 | // regex for ipv6 with cidr
8 | export const correctIpv6CidrRegex = new RegExp(
9 | /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))$/i
10 | )
11 |
12 | export const correctIPv4CidrRegex = new RegExp(
13 | /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/\d{1,2}$/i
14 | )
15 |
16 | export const ipv4AddressRegex = new RegExp(
17 | /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$/i
18 | )
19 |
--------------------------------------------------------------------------------
/src/util/unixTime.ts:
--------------------------------------------------------------------------------
1 | export const timeConverter = (UNIX_timestamp: number) => {
2 | const a = new Date(UNIX_timestamp * 1000)
3 | const months = [
4 | 'Jan',
5 | 'Feb',
6 | 'Mar',
7 | 'Apr',
8 | 'May',
9 | 'Jun',
10 | 'Jul',
11 | 'Aug',
12 | 'Sep',
13 | 'Oct',
14 | 'Nov',
15 | 'Dec',
16 | ]
17 | const year = a.getFullYear()
18 | const month = months[a.getMonth()]
19 | const date = a.getDate()
20 | const hour = a.getHours()
21 | const min = a.getMinutes()
22 | const sec = a.getSeconds()
23 | const time =
24 | date + ' ' + month + ', ' + year + ' - ' + hour + ':' + min + ':' + sec
25 | return time
26 | }
27 | export const datePickerConverter = (UNIX_timestamp: number) => {
28 | const a = new Date(UNIX_timestamp * 1000)
29 | const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
30 | const year = a.getFullYear()
31 | const month =
32 | months[a.getMonth()] < 10
33 | ? '0' + months[a.getMonth()]
34 | : months[a.getMonth()]
35 | const date = a.getDate() < 10 ? '0' + a.getDate() : a.getDate()
36 | const hour = a.getHours() < 10 ? '0' + a.getHours() : a.getHours()
37 | const min = a.getMinutes() < 10 ? '0' + a.getMinutes() : a.getMinutes()
38 | const time = year + '-' + month + '-' + date + 'T' + hour + ':' + min
39 | return time
40 | }
41 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.paths.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 | "noFallthroughCasesInSwitch": true
23 | },
24 | "include": [
25 | "src"
26 | ],
27 | "exclude": [
28 | "src/Components"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/tsconfig.paths.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "~util/*": ["./src/util/*"],
6 | "~components/*": ["./src/components/*"],
7 | "~store/*": ["./src/store/*"],
8 | "~modules/*": ["./src/store/modules/*"]
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------