├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── enhancement.md └── workflows │ ├── catalog.yml │ ├── nr1_lib_deprecations.yml │ ├── pr.yml │ ├── release.yml │ └── repolinter.yml ├── .gitignore ├── .prettierrc.js ├── .releaserc ├── .stylelintrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── THIRD_PARTY_NOTICES.md ├── TODO ├── catalog ├── config.json ├── documentation.md └── screenshots │ ├── nr1-network-telemetry-1.png │ └── nr1-network-telemetry-2.png ├── cla.md ├── commitlint.config.js ├── docs └── CHANGELOG.md ├── icon.png ├── jest.config.js ├── launchers └── network-telemetry-launcher │ ├── icon.png │ └── nr1.json ├── nerdlets └── network-telemetry-overview │ ├── MainMenu.jsx │ ├── common.jsx │ ├── constants.js │ ├── fetch.js │ ├── index.jsx │ ├── ip-address.jsx │ ├── ipfix-detail.jsx │ ├── ipfix.jsx │ ├── network-summary.jsx │ ├── network-telemetry-nerdlet.jsx │ ├── nr1.json │ ├── sflow.jsx │ └── styles.scss ├── nr1.json ├── package-lock.json ├── package.json ├── src ├── components │ ├── account-dropdown │ │ └── index.jsx │ └── time-range │ │ └── index.js ├── constants.js └── lib │ ├── accounts-with-data.js │ ├── bytes-to-size.js │ ├── find-related-account-with.js │ ├── nrdb-query.js │ └── nrql.js ├── test ├── test.test.js └── utils │ ├── mocks │ └── styleMock.jsx │ └── setupTests.jsx └── third_party_manifest.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties", 4 | "@babel/plugin-proposal-object-rest-spread", 5 | "@babel/plugin-transform-runtime", 6 | "add-react-displayname" 7 | ], 8 | "presets": ["@babel/preset-env", "@babel/preset-react"], 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | .eslintrc.js 3 | .git/** 4 | dist/** 5 | coverage/** 6 | nerdlets/**/nr1.json 7 | node_modules/** 8 | package-lock.json 9 | package.json 10 | test-results/** 11 | tmp/** 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | 'jest/globals': true, 5 | jasmine: true, 6 | 'webdriverio/wdio': true 7 | }, 8 | extends: [ 9 | 'plugin:jsx-a11y/recommended', 10 | 'plugin:react-perf/recommended', 11 | 'plugin:react/recommended', 12 | 'standard', 13 | 'prettier', 14 | 'prettier/react', 15 | 'prettier/standard', 16 | 'prettier/@typescript-eslint', 17 | 'plugin:jasmine/recommended' 18 | ], 19 | globals: { 20 | USE_LOCAL_PROXY: false 21 | }, 22 | overrides: [ 23 | { 24 | files: ['**/*.ts', '**/*.tsx'], 25 | rules: { 26 | 'no-unused-vars': 'off', 27 | 'react/prop-types': 'off' 28 | } 29 | }, 30 | { 31 | files: ['**/*.jsx', '**/*.spec.*', '**/*.stories.*'], 32 | rules: { 33 | 'react-perf/jsx-no-new-array-as-prop': 'off', 34 | 'react-perf/jsx-no-new-function-as-prop': 'off', 35 | 'react-perf/jsx-no-new-object-as-prop': 'off' 36 | } 37 | } 38 | ], 39 | parser: '@typescript-eslint/parser', 40 | plugins: [ 41 | //'filenames', 42 | 'promise', 43 | 'react', 44 | 'react-perf', 45 | 'jest', 46 | 'standard', 47 | 'prettier', 48 | 'jasmine', 49 | 'webdriverio' 50 | ], 51 | rules: { 52 | // Filenames should match the exported item 53 | //'filenames/match-exported': ['warn', ['kebab']], 54 | // snake-case. You can start with a _ if you need to. Dots are ok too. 55 | //'filenames/match-regex': ['error', '^_?[a-z0-9\\-\\.]+$'], 56 | 57 | 'no-alert': 'error', 58 | 'no-console': [ 59 | 'error', 60 | { 61 | allow: ['info', 'warn', 'debug', 'error', 'assert'] 62 | } 63 | ], 64 | 'prefer-const': 'error', 65 | 'react-perf/jsx-no-new-array-as-prop': 'warn', 66 | 'react-perf/jsx-no-new-function-as-prop': 'warn', 67 | 'react-perf/jsx-no-new-object-as-prop': 'warn', 68 | 'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }], 69 | 'react/jsx-sort-props': 'warn', 70 | 'sort-imports': 'warn', 71 | 'sort-keys': 'warn', 72 | 'jasmine/no-spec-dupes': ['error', 'branch'] 73 | }, 74 | settings: { 75 | react: { 76 | version: '16' 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Describe a scenario in which this project behaves unexpectedly 4 | title: '' 5 | labels: bug, needs-triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | [NOTE]: # ( ^^ Provide a general summary of the issue in the title above. ^^ ) 11 | 12 | ## Description 13 | 14 | [NOTE]: # ( Describe the problem you're encountering. ) 15 | [TIP]: # ( Do NOT give us access or passwords to your New Relic account or API keys! ) 16 | 17 | ## Steps to Reproduce 18 | 19 | [NOTE]: # ( Please be as specific as possible. ) 20 | 21 | ## Expected Behaviour 22 | 23 | [NOTE]: # ( Tell us what you expected to happen. ) 24 | 25 | ## Relevant Logs / Console output 26 | 27 | [NOTE]: # ( Please provide specifics of the local error logs, Browser Dev Tools console, etc. if appropriate and possible. ) 28 | 29 | ## Your Environment 30 | 31 | [TIP]: # ( Include as many relevant details about your environment as possible. ) 32 | 33 | * NR1 CLI version used: 34 | * Browser name and version: 35 | * Operating System and version: 36 | 37 | ## Additional context 38 | 39 | [TIP]: # ( Add any other context about the problem here. ) 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Suggest an idea for a future version of this project 4 | title: '' 5 | labels: enhancement, needs-triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | [NOTE]: # ( ^^ Provide a general summary of the request in the title above. ^^ ) 11 | 12 | ## Summary 13 | 14 | [NOTE]: # ( Provide a brief overview of what the new feature is all about. ) 15 | 16 | ## Desired Behaviour 17 | 18 | [NOTE]: # ( Tell us how the new feature should work. Be specific. ) 19 | [TIP]: # ( Do NOT give us access or passwords to your New Relic account or API keys! ) 20 | 21 | ## Possible Solution 22 | 23 | [NOTE]: # ( Not required. Suggest how to implement the addition or change. ) 24 | 25 | ## Additional context 26 | 27 | [TIP]: # ( Why does this feature matter to you? What unique circumstances do you have? ) 28 | -------------------------------------------------------------------------------- /.github/workflows/catalog.yml: -------------------------------------------------------------------------------- 1 | name: Catalog 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | appName: 7 | description: "NR1 Nerdpack Name" 8 | required: true 9 | version: 10 | description: "Version to update" 11 | required: true 12 | ref: 13 | description: "Commit SHA to update the submodule to" 14 | required: true 15 | user: 16 | description: "User who initiated the deployment" 17 | required: true 18 | action: 19 | description: "Action to take with submodule. Possible values: add, update" 20 | required: true 21 | default: "update" 22 | url: 23 | description: "If action == `add`, must supply URL of repo" 24 | required: false 25 | jobs: 26 | job-check-workflow-dispatch-inputs: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - run: | 30 | echo "appName: ${{ github.event.inputs.appName }}" 31 | echo "version: ${{ github.event.inputs.version }}" 32 | echo "ref: ${{ github.event.inputs.ref }}" 33 | echo "user: ${{ github.event.inputs.user }}" 34 | echo "action: ${{ github.event.inputs.action }}" 35 | echo "url: ${{ github.event.inputs.url }}" 36 | 37 | job-trigger-catalog-workflow: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Invoke nr1-catalog PR workflow 41 | uses: benc-uk/workflow-dispatch@v1 42 | with: 43 | workflow: Generate Catalog PR 44 | repo: newrelic/nr1-catalog 45 | token: ${{ secrets.OPENSOURCE_BOT_TOKEN }} 46 | ref: master 47 | inputs: '{ "appName": "${{ github.event.inputs.appName }}", "version": "${{ github.event.inputs.version }}", "ref": "${{ github.event.inputs.ref }}", "user": "${{ github.event.inputs.user }}", "action": "${{ github.event.inputs.action }}", "url": "${{ github.event.inputs.url }}" }' 48 | -------------------------------------------------------------------------------- /.github/workflows/nr1_lib_deprecations.yml: -------------------------------------------------------------------------------- 1 | name: NR1 Library Deprecation Checks 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | nr1_lib_deprecations: 7 | name: Run NR1 Library Deprecation Checks 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Test Default Branch 11 | id: default-branch 12 | uses: actions/github-script@v2 13 | with: 14 | script: | 15 | const data = await github.repos.get(context.repo) 16 | return data.data && data.data.default_branch === context.ref.split('/').slice(-1)[0] 17 | - name: Checkout Self 18 | if: ${{ steps.default-branch.outputs.result == 'true' }} 19 | uses: actions/checkout@v2 20 | - name: Run Repolinter 21 | if: ${{ steps.default-branch.outputs.result == 'true' }} 22 | uses: newrelic/repolinter-action@v1 23 | with: 24 | output_name: 'NR1 library deprecation issues' 25 | label_name: 'nr1-deprecations' 26 | label_color: '800000' 27 | # FIXME: Replace with the appropriate ruleset URL 28 | config_url: https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/nr1-lib-deprecations.yml 29 | output_type: issue 30 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | repository_dispatch: 9 | types: [pull-request] 10 | 11 | jobs: 12 | checkout-and-build-pr: 13 | name: checkout-and-build-pr 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup node 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 16 23 | 24 | - name: Cache node_modules 25 | id: cache-node-modules 26 | uses: actions/cache@v1 27 | env: 28 | cache-name: node-modules 29 | with: 30 | path: ~/.npm 31 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-${{ env.cache-name }}- 34 | 35 | - name: Install Dependencies 36 | run: npm ci 37 | 38 | - name: Install NR1 CLI 39 | run: curl -s https://cli.nr-ext.net/installer.sh | sudo bash 40 | 41 | - name: NR1 Nerdpack Build 42 | run: | 43 | nr1 nerdpack:build 44 | 45 | eslint: 46 | name: eslint 47 | needs: checkout-and-build-pr 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Checkout repo 51 | uses: actions/checkout@v2 52 | 53 | - name: Setup node 54 | uses: actions/setup-node@v1 55 | with: 56 | node-version: 16 57 | 58 | - name: Cache node_modules 59 | id: cache-node-modules 60 | uses: actions/cache@v1 61 | env: 62 | cache-name: node-modules 63 | with: 64 | path: ~/.npm 65 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 66 | restore-keys: | 67 | ${{ runner.os }}-${{ env.cache-name }}- 68 | 69 | - name: Install Dependencies 70 | run: npm ci 71 | 72 | - name: Run eslint-check and generate report 73 | id: eslint-check 74 | run: | 75 | npm run eslint-check -- --output-file eslint_report.json --format json 76 | continue-on-error: true 77 | 78 | - name: Annotate Lint Results 79 | uses: ataylorme/eslint-annotate-action@1.0.4 80 | with: 81 | repo-token: ${{ secrets.GITHUB_TOKEN }} 82 | report-json: eslint_report.json 83 | continue-on-error: true 84 | 85 | - name: Check eslint-check outcome 86 | if: steps.eslint-check.outcome != 'success' 87 | run: | 88 | echo "::error::eslint-check failed. View output of _Run eslint-check and generate report_ step" 89 | exit 1 90 | 91 | test: 92 | name: test 93 | needs: checkout-and-build-pr 94 | runs-on: ubuntu-latest 95 | steps: 96 | - name: Checkout repo 97 | uses: actions/checkout@v2 98 | 99 | - name: Setup node 100 | uses: actions/setup-node@v1 101 | with: 102 | node-version: 16 103 | 104 | - name: Cache node_modules 105 | id: cache-node-modules 106 | uses: actions/cache@v1 107 | env: 108 | cache-name: node-modules 109 | with: 110 | path: ~/.npm 111 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 112 | restore-keys: | 113 | ${{ runner.os }}-${{ env.cache-name }}- 114 | 115 | - name: Install Dependencies 116 | run: npm ci 117 | 118 | - name: Run npm test 119 | run: npm test 120 | 121 | validate-nerdpack: 122 | name: validate nerdpack 123 | needs: checkout-and-build-pr 124 | runs-on: ubuntu-latest 125 | steps: 126 | - name: Checkout repo 127 | uses: actions/checkout@v2 128 | 129 | - name: Validate Open Source Files 130 | uses: newrelic/validate-nerdpack-action@v1 131 | 132 | - name: Install NR1 CLI 133 | run: | 134 | curl -s https://cli.nr-ext.net/installer.sh | sudo bash 135 | 136 | - name: Validate Nerdpack Schema 137 | run: | 138 | nr1 nerdpack:validate 139 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | repository_dispatch: 8 | types: [semantic-release] 9 | 10 | env: 11 | THIRD_PARTY_GIT_AUTHOR_EMAIL: opensource+bot@newrelic.com 12 | THIRD_PARTY_GIT_AUTHOR_NAME: nr-opensource-bot 13 | 14 | jobs: 15 | job-checkout-and-build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout repo 19 | uses: actions/checkout@v2 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: 16 25 | 26 | - name: Cache node_modules 27 | id: cache-node-modules 28 | uses: actions/cache@v1 29 | env: 30 | cache-name: node-modules 31 | with: 32 | path: ~/.npm 33 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 34 | restore-keys: | 35 | ${{ runner.os }}-${{ env.cache-name }}- 36 | 37 | - name: Install Dependencies 38 | run: npm ci 39 | 40 | - name: Install NR1 CLI 41 | run: curl -s https://cli.nr-ext.net/installer.sh | sudo bash 42 | 43 | - name: NR1 Nerdpack Build 44 | run: | 45 | nr1 nerdpack:build 46 | nr1 nerdpack:validate 47 | 48 | job-generate-third-party-notices: 49 | runs-on: ubuntu-latest 50 | needs: job-checkout-and-build 51 | steps: 52 | # Checkout fetch-depth: 2 because there's a check to see if package.json 53 | # was updated, and need at least 2 commits for the check to function properly 54 | - name: Checkout repo 55 | uses: actions/checkout@v2 56 | with: 57 | fetch-depth: 2 58 | 59 | - name: Setup Node.js 60 | uses: actions/setup-node@v1 61 | with: 62 | node-version: 16 63 | 64 | - name: Download Cached Deps 65 | id: cache-node-modules 66 | uses: actions/cache@v1 67 | env: 68 | cache-name: node-modules 69 | with: 70 | path: ~/.npm 71 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 72 | restore-keys: | 73 | ${{ runner.os }}-${{ env.cache-name }}- 74 | 75 | - name: Install Dependencies 76 | run: npm ci 77 | 78 | - name: Install OSS CLI 79 | run: | 80 | sudo npm install -g @newrelic/newrelic-oss-cli 81 | 82 | - name: Generate Third Party Notices 83 | id: generate-notices 84 | run: | 85 | if [ ! -f "third_party_manifest.json" ]; then 86 | echo "::error::third_party_manifest.json is missing. Must generate using the newrelic-oss-cli." 87 | exit 1 88 | fi 89 | 90 | # latest commit 91 | LATEST_COMMIT=$(git rev-parse HEAD) 92 | 93 | # latest commit where package.json was changed 94 | LAST_CHANGED_COMMIT=$(git log -1 --format=format:%H --full-diff package.json) 95 | 96 | if [ $LAST_CHANGED_COMMIT = $LATEST_COMMIT ]; then 97 | git config user.email "${{ env.THIRD_PARTY_GIT_AUTHOR_EMAIL }}" 98 | git config user.name "${{ env.THIRD_PARTY_GIT_AUTHOR_NAME }}" 99 | 100 | oss third-party manifest 101 | oss third-party notices 102 | 103 | git add third_party_manifest.json 104 | git add THIRD_PARTY_NOTICES.md 105 | 106 | git commit -m 'chore: update third-party manifest and notices [skip ci]' 107 | echo "::set-output name=commit::true" 108 | else 109 | echo "No change in package.json, not regenerating third-party notices" 110 | fi 111 | 112 | - name: Temporarily disable "required_pull_request_reviews" branch protection 113 | id: disable-branch-protection 114 | if: always() 115 | uses: actions/github-script@v1 116 | with: 117 | github-token: ${{ secrets.OPENSOURCE_BOT_TOKEN }} 118 | previews: luke-cage-preview 119 | script: | 120 | const result = await github.repos.updateBranchProtection({ 121 | owner: context.repo.owner, 122 | repo: context.repo.repo, 123 | branch: 'main', 124 | required_status_checks: null, 125 | restrictions: null, 126 | enforce_admins: null, 127 | required_pull_request_reviews: null 128 | }) 129 | console.log("Result:", result) 130 | 131 | - name: Push Commit 132 | if: steps.generate-notices.outputs.commit == 'true' 133 | uses: ad-m/github-push-action@v0.6.0 134 | with: 135 | github_token: ${{ secrets.OPENSOURCE_BOT_TOKEN }} 136 | branch: main 137 | 138 | - name: Re-enable "required_pull_request_reviews" branch protection 139 | id: enable-branch-protection 140 | if: always() 141 | uses: actions/github-script@v1 142 | with: 143 | github-token: ${{ secrets.OPENSOURCE_BOT_TOKEN }} 144 | previews: luke-cage-preview 145 | script: | 146 | const result = await github.repos.updateBranchProtection({ 147 | owner: context.repo.owner, 148 | repo: context.repo.repo, 149 | branch: 'main', 150 | required_status_checks: null, 151 | restrictions: null, 152 | enforce_admins: null, 153 | required_pull_request_reviews: { 154 | required_approving_review_count: 1 155 | } 156 | }) 157 | console.log("Result:", result) 158 | 159 | job-generate-release: 160 | runs-on: ubuntu-latest 161 | needs: [job-checkout-and-build, job-generate-third-party-notices] 162 | steps: 163 | # Checkout ref: main because previous job committed third_party_notices and 164 | # we need to checkout main to pick up that commit 165 | - name: Checkout repo 166 | uses: actions/checkout@v2 167 | with: 168 | ref: main 169 | 170 | - name: Setup Node.js 171 | uses: actions/setup-node@v1 172 | with: 173 | node-version: 16 174 | 175 | - name: Install dependencies 176 | run: npm ci 177 | 178 | - name: Temporarily disable "required_pull_request_reviews" branch protection 179 | id: disable-branch-protection 180 | if: always() 181 | uses: actions/github-script@v1 182 | with: 183 | github-token: ${{ secrets.OPENSOURCE_BOT_TOKEN }} 184 | previews: luke-cage-preview 185 | script: | 186 | const result = await github.repos.updateBranchProtection({ 187 | owner: context.repo.owner, 188 | repo: context.repo.repo, 189 | branch: 'main', 190 | required_status_checks: null, 191 | restrictions: null, 192 | enforce_admins: null, 193 | required_pull_request_reviews: null 194 | }) 195 | console.log("Result:", result) 196 | 197 | - name: Run semantic-release 198 | env: 199 | # Use nr-opensource-bot for authoring commits done by 200 | # semantic-release (rather than using @semantic-release-bot) 201 | GIT_AUTHOR_NAME: "nr-opensource-bot" 202 | GIT_AUTHOR_EMAIL: "opensource+bot@newrelic.com" 203 | GIT_COMMITTER_NAME: "nr-opensource-bot" 204 | GIT_COMMITTER_EMAIL: "opensource+bot@newrelic.com" 205 | GITHUB_TOKEN: ${{ secrets.OPENSOURCE_BOT_TOKEN }} 206 | run: npx semantic-release@^18.0.0 207 | 208 | - name: Re-enable "required_pull_request_reviews" branch protection 209 | id: enable-branch-protection 210 | if: always() 211 | uses: actions/github-script@v1 212 | with: 213 | github-token: ${{ secrets.OPENSOURCE_BOT_TOKEN }} 214 | previews: luke-cage-preview 215 | script: | 216 | const result = await github.repos.updateBranchProtection({ 217 | owner: context.repo.owner, 218 | repo: context.repo.repo, 219 | branch: 'main', 220 | required_status_checks: null, 221 | restrictions: null, 222 | enforce_admins: null, 223 | required_pull_request_reviews: { 224 | required_approving_review_count: 1 225 | } 226 | }) 227 | console.log("Result:", result) 228 | -------------------------------------------------------------------------------- /.github/workflows/repolinter.yml: -------------------------------------------------------------------------------- 1 | name: Repolinter Action 2 | 3 | # NOTE: This workflow will ONLY check the default branch! 4 | # Currently there is no elegant way to specify the default 5 | # branch in the event filtering, so branches are instead 6 | # filtered in the "Test Default Branch" step. 7 | on: [push, workflow_dispatch] 8 | 9 | jobs: 10 | repolint: 11 | name: Run Repolinter 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Test Default Branch 15 | id: default-branch 16 | uses: actions/github-script@v2 17 | with: 18 | script: | 19 | const data = await github.repos.get(context.repo) 20 | return data.data && data.data.default_branch === context.ref.split('/').slice(-1)[0] 21 | - name: Checkout Self 22 | if: ${{ steps.default-branch.outputs.result == 'true' }} 23 | uses: actions/checkout@v2 24 | - name: Run Repolinter 25 | if: ${{ steps.default-branch.outputs.result == 'true' }} 26 | uses: newrelic/repolinter-action@v1 27 | with: 28 | config_url: https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/new-relic-one-catalog-project.json 29 | output_type: issue 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | dist 3 | node_modules 4 | coverage 5 | tmp 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | trailingComma: 'es5', 4 | tabWidth: 2, 5 | singleQuote: true, 6 | useTabs: false, 7 | jsxSingleQuote: true, 8 | }; 9 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main"], 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | ["@semantic-release/changelog", { 7 | "changelogFile": "docs/CHANGELOG.md" 8 | }], 9 | "@semantic-release/github", 10 | ["@semantic-release/npm", { 11 | "npmPublish": false 12 | }], 13 | ["@semantic-release/git", { 14 | "assets": ["docs", "package.json", "package-lock.json"], 15 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 16 | }] 17 | ], 18 | "dryRun": false, 19 | "debug": true 20 | } 21 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-prettier" 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome. Before contributing please read the 4 | [code of conduct](blog/main/CODE_OF_CONDUCT.md) and [search the issue tracker](issues); your issue may have already been discussed or fixed in `main`. To contribute, 5 | [fork](https://help.github.com/articles/fork-a-repo/) this repository, commit your changes, and [send a Pull Request](https://help.github.com/articles/using-pull-requests/). 6 | 7 | Note that our [code of conduct](blog/main/CODE_OF_CONDUCT.md) applies to all platforms and venues related to this project; please follow it in all your interactions with the project and its participants. 8 | 9 | ## Feature Requests 10 | 11 | Feature requests should be submitted in the [Issue tracker](issues), with a description of the expected behavior & use case, where they’ll remain closed until sufficient interest, [e.g. :+1: reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/), has been [shown by the community](issues?q=label%3A%22votes+needed%22+sort%3Areactions-%2B1-desc). 12 | Before submitting an Issue, please search for similar ones in the 13 | [closed issues](issues?q=is%3Aissue+is%3Aclosed+label%3Aenhancement). 14 | 15 | ## Pull Requests 16 | 17 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 18 | 2. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 19 | 3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 20 | 21 | ## Contributor License Agreement 22 | 23 | Keep in mind that when you submit your Pull Request, you'll need to sign the CLA via the click-through using CLA-Assistant. If you'd like to execute our corporate CLA, or if you have any questions, please drop us an email at opensource@newrelic.com. 24 | 25 | For more information about CLAs, please check out Alex Russell’s excellent post, 26 | [“Why Do I Need to Sign This?”](https://infrequently.org/2008/06/why-do-i-need-to-sign-this/). 27 | 28 | # Slack 29 | 30 | For contributors and maintainers of open source projects hosted by New Relic, we host a public Slack with a channel dedicated to this project. If you are contributing to this project, you're welcome to request access to that community space. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 New Relic, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![New Relic One Catalog Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/New_Relic_One_Catalog_Project.png)](https://opensource.newrelic.com/oss-category/#new-relic-one-catalog-project) 2 | 3 | # NR1 Network Telemetry (nr1-network-telemetry) 4 | 5 | ![CI](https://github.com/newrelic/nr1-network-telemetry/workflows/CI/badge.svg) ![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/newrelic/nr1-network-telemetry?include_prereleases&sort=semver) [![Snyk](https://snyk.io/test/github/newrelic/nr1-network-telemetry/badge.svg)](https://snyk.io/test/github/newrelic/nr1-network-telemetry) 6 | 7 | ## Usage 8 | 9 | The Network Telemetry application displays the data collected by the [`Network Telemetry`](https://github.com/newrelic/nri-network-telemetry) Integration. 10 | 11 | ### IPFIX Visualization 12 | 13 | Quickly identify where traffic is coming from and going to across your routing tier. 14 | 15 | ![Screenshot #1](./catalog/screenshots/nr1-network-telemetry-1.png) 16 | 17 | ### Sflow Visualization 18 | 19 | View the top talkers in your network, and who they are communicating with. 20 | 21 | ![Screenshot #2](./catalog/screenshots/nr1-network-telemetry-2.png) 22 | 23 | ## Open Source License 24 | 25 | This project is distributed under the [Apache 2 license](./LICENSE). 26 | 27 | ## Dependencies 28 | 29 | Requires [`New Relic Infrastructure`](https://newrelic.com/products/infrastructure) and the [`Network Telemetry`](https://github.com/newrelic/nri-network-telemetry) Integration. 30 | 31 | ## Getting started 32 | 33 | First, ensure that you have [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [NPM](https://www.npmjs.com/get-npm) installed. If you're unsure whether you have one or both of them installed, run the following command(s) (If you have them installed these commands will return a version number, if not, the commands won't be recognized): 34 | 35 | ```bash 36 | git --version 37 | npm -v 38 | ``` 39 | 40 | Next, clone this repository and run the following scripts: 41 | 42 | ```bash 43 | nr1 nerdpack:clone -r https://github.com/newrelic/nr1-network-telemetry.git 44 | cd nr1-network-telemetry 45 | nr1 nerdpack:uuid -gf 46 | npm install 47 | npm start 48 | ``` 49 | 50 | Visit [https://one.newrelic.com/?nerdpacks=local](https://one.newrelic.com/?nerdpacks=local), navigate to the Nerdpack, and :sparkles: 51 | 52 | ## Deploying this Nerdpack 53 | 54 | Open a command prompt in the nerdpack's directory and run the following commands. 55 | 56 | ```bash 57 | # To create a new uuid for the nerdpack so that you can deploy it to your account: 58 | # nr1 nerdpack:uuid -g [--profile=your_profile_name] 59 | 60 | # To see a list of API keys / profiles available in your development environment: 61 | # nr1 profiles:list 62 | 63 | nr1 nerdpack:publish [--profile=your_profile_name] 64 | nr1 nerdpack:deploy [-c [DEV|BETA|STABLE]] [--profile=your_profile_name] 65 | nr1 nerdpack:subscribe [-c [DEV|BETA|STABLE]] [--profile=your_profile_name] 66 | ``` 67 | 68 | Visit [https://one.newrelic.com](https://one.newrelic.com), navigate to the Nerdpack, and :sparkles: 69 | 70 | ## Community Support 71 | 72 | New Relic hosts and moderates an online forum where you can interact with New Relic employees as well as other customers to get help and share best practices. Like all New Relic open source community projects, there's a related topic in the New Relic Explorers Hub. You can find this project's topic/threads here: 73 | 74 | [https://discuss.newrelic.com/t/network-telemetry-nerdpack/90561](https://discuss.newrelic.com/t/network-telemetry-nerdpack/90561) 75 | 76 | Please do not report issues with Network Telemetry to New Relic Global Technical Support. Instead, visit the [`Explorers Hub`](https://discuss.newrelic.com/c/build-on-new-relic) for troubleshooting and best-practices. 77 | 78 | ## Issues / Enhancement Requests 79 | 80 | Issues and enhancement requests can be submitted in the [Issues tab of this repository](https://github.com/newrelic/nr1-network-telemetry/issues). Please search for and review the existing open issues before submitting a new issue. 81 | 82 | ## Security 83 | 84 | As noted in our [security policy](https://github.com/newrelic/nr1-network-telemetry/security/policy), New Relic is committed to the privacy and security of our customers and their data. We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals. 85 | 86 | If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [HackerOne](https://hackerone.com/newrelic). 87 | 88 | ## Contributing 89 | 90 | Contributions are welcome (and if you submit a Enhancement Request, expect to be invited to contribute it yourself :grin:). Please review our [Contributors Guide](./CONTRIBUTING.md). 91 | 92 | Keep in mind that when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. If you'd like to execute our corporate CLA, or if you have any questions, please drop us an email at opensource+nr1-network-telemetry@newrelic.com. 93 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | # Open items 2 | 3 | ## MVP 4 | 5 | * Right side menu min-width 6 | 7 | ## Nice to have 8 | 9 | * Right side menu padding/margin/format fixes 10 | -------------------------------------------------------------------------------- /catalog/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tagline": "IPfix and Sflow visualization", 3 | "repository": "https://github.com/newrelic/nr1-network-telemetry.git", 4 | "details": "The Network Telemetry application displays the data collected by the Network Telemetry Integration.\n\nUsing the IPfix visualization, you can quickly identify where traffic is coming from and going across your routing tier.\n\nUsing the Sflow visualization, you can view the top talkers in your network, and who they are communicating with.", 5 | "categoryTerms": ["os", "infrastructure"], 6 | "keywords": ["visualizations", "telemetry", "networking"], 7 | "support": { 8 | "issues": { 9 | "url": "https://github.com/newrelic/nr1-network-telemetry/issues" 10 | }, 11 | "email": { 12 | "address": "opensource+nr1-network-telemetry@newrelic.com" 13 | }, 14 | "community": { 15 | "url": "https://discuss.newrelic.com/t/network-telemetry-nerdpack/90561" 16 | } 17 | }, 18 | "whatsNew": "- Feat: limit width of src in network summary table" 19 | } 20 | -------------------------------------------------------------------------------- /catalog/documentation.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | The Network Telemetry application displays the data collected by the [`Network Telemetry`](https://github.com/newrelic/nri-network-telemetry) Integration. 4 | 5 | ### IPFIX Visualization 6 | 7 | Quickly identify where traffic is coming from and going to across your routing tier. 8 | 9 | ### Sflow Visualization 10 | 11 | View the top talkers in your network, and who they are communicating with. 12 | 13 | ## Open Source License 14 | 15 | This project is distributed under the [Apache 2 license](https://github.com/newrelic/nr1-network-telemetry/blog/main/LICENSE). 16 | 17 | ## Dependencies 18 | 19 | Requires [`New Relic Infrastructure`](https://newrelic.com/products/infrastructure) and the [`Network Telemetry`](https://github.com/newrelic/nri-network-telemetry) Integration. 20 | 21 | ## Getting started 22 | 23 | First, ensure that you have [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [NPM](https://www.npmjs.com/get-npm) installed. If you're unsure whether you have one or both of them installed, run the following command(s) (If you have them installed these commands will return a version number, if not, the commands won't be recognized): 24 | 25 | ```bash 26 | git --version 27 | npm -v 28 | ``` 29 | 30 | Next, clone this repository and run the following scripts: 31 | 32 | ```bash 33 | nr1 nerdpack:clone -r https://github.com/newrelic/nr1-network-telemetry.git 34 | cd nr1-network-telemetry 35 | nr1 nerdpack:serve 36 | ``` 37 | 38 | Visit [https://one.newrelic.com/?nerdpacks=local](https://one.newrelic.com/?nerdpacks=local), navigate to the Nerdpack, and :sparkles: 39 | 40 | ## Deploying this Nerdpack 41 | 42 | Open a command prompt in the nerdpack's directory and run the following commands. 43 | 44 | ```bash 45 | # To create a new uuid for the nerdpack so that you can deploy it to your account: 46 | # nr1 nerdpack:uuid -g [--profile=your_profile_name] 47 | 48 | # To see a list of API keys / profiles available in your development environment: 49 | # nr1 profiles:list 50 | 51 | nr1 nerdpack:publish [--profile=your_profile_name] 52 | nr1 nerdpack:deploy [-c [DEV|BETA|STABLE]] [--profile=your_profile_name] 53 | nr1 nerdpack:subscribe [-c [DEV|BETA|STABLE]] [--profile=your_profile_name] 54 | ``` 55 | 56 | Visit [https://one.newrelic.com](https://one.newrelic.com), navigate to the Nerdpack, and :sparkles: 57 | 58 | ## Community Support 59 | 60 | New Relic hosts and moderates an online forum where you can interact with New Relic employees as well as other customers to get help and share best practices. Like all New Relic open source community projects, there's a related topic in the New Relic Explorers Hub. You can find this project's topic/threads here: 61 | 62 | [https://discuss.newrelic.com/t/network-telemetry-nerdpack/90561](https://discuss.newrelic.com/t/network-telemetry-nerdpack/90561) 63 | 64 | Please do not report issues with Network Telemetry to New Relic Global Technical Support. Instead, visit the [`Explorers Hub`](https://discuss.newrelic.com/c/build-on-new-relic) for troubleshooting and best-practices. 65 | 66 | ## Issues / Enhancement Requests 67 | 68 | Issues and enhancement requests can be submitted in the [Issues tab of this repository](https://github.com/newrelic/nr1-network-telemetry/issues). Please search for and review the existing open issues before submitting a new issue. 69 | 70 | ## Contributing 71 | 72 | Contributions are welcome (and if you submit a Enhancement Request, expect to be invited to contribute it yourself :grin:). Please review our [Contributors Guide](https://github.com/newrelic/nr1-network-telemetry/blob/main/CONTRIBUTING.md). 73 | 74 | Keep in mind that when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. If you'd like to execute our corporate CLA, or if you have any questions, please drop us an email at opensource+nr1-network-telemetry@newrelic.com. 75 | -------------------------------------------------------------------------------- /catalog/screenshots/nr1-network-telemetry-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/nr1-network-telemetry/f9c88494bdb67987763d50514825aabba6344167/catalog/screenshots/nr1-network-telemetry-1.png -------------------------------------------------------------------------------- /catalog/screenshots/nr1-network-telemetry-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/nr1-network-telemetry/f9c88494bdb67987763d50514825aabba6344167/catalog/screenshots/nr1-network-telemetry-2.png -------------------------------------------------------------------------------- /cla.md: -------------------------------------------------------------------------------- 1 | # NEW RELIC, INC. 2 | ## INDIVIDUAL CONTRIBUTOR LICENSE AGREEMENT 3 | Thank you for your interest in contributing to the open source projects of New Relic, Inc. (“New Relic”). In order to clarify the intellectual property license granted with Contributions from any person or entity, New Relic must have a Contributor License Agreement ("Agreement") on file that has been signed by each Contributor, indicating agreement to the license terms below. This Agreement is for your protection as a Contributor as well as the protection of New Relic; it does not change your rights to use your own Contributions for any other purpose. 4 | 5 | You accept and agree to the following terms and conditions for Your present and future Contributions submitted to New Relic. Except for the licenses granted herein to New Relic and recipients of software distributed by New Relic, You reserve all right, title, and interest in and to Your Contributions. 6 | 7 | ## Definitions. 8 | 1. "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is entering into this Agreement with New Relic. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 9 | 2. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to New Relic for inclusion in, or documentation of, any of the products managed or maintained by New Relic (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to New Relic or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, New Relic for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." 10 | 3. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. 11 | 4. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. 12 | 5. You represent that You are legally entitled to grant the above licenses. If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You represent that You have received permission to make Contributions on behalf of that employer, that Your employer has waived such rights for Your Contributions to New Relic, or that Your employer has executed a separate Agreement with New Relic. 13 | 6. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which You are personally aware and which are associated with any part of Your Contributions. 14 | 7. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 15 | 8. Should You wish to submit work that is not Your original creation, You may submit it to New Relic separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". 16 | 9. You agree to notify New Relic of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect. -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'scope-case': [0], 5 | 'subject-case': [0], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.8.3](https://github.com/newrelic/nr1-network-telemetry/compare/v0.8.2...v0.8.3) (2022-02-25) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * remove uuid update ([4613a70](https://github.com/newrelic/nr1-network-telemetry/commit/4613a707d897579da2c28a4c07a279dcb7ac3517)) 7 | * update node version in workflows, update errors to error ([87ccf14](https://github.com/newrelic/nr1-network-telemetry/commit/87ccf1499c7fb1958cf88ceaed87c94165d61eed)) 8 | 9 | ## [0.8.2](https://github.com/newrelic/nr1-network-telemetry/compare/v0.8.1...v0.8.2) (2021-11-10) 10 | 11 | 12 | ### Bug Fixes 13 | 14 | * word spacing in docs ([98e10ac](https://github.com/newrelic/nr1-network-telemetry/commit/98e10acda825d2da8c44d10ee0fa1106e1fce431)) 15 | 16 | ## [0.8.1](https://github.com/newrelic/nr1-network-telemetry/compare/v0.8.0...v0.8.1) (2021-11-09) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * bump version for release ([256b60a](https://github.com/newrelic/nr1-network-telemetry/commit/256b60a5af13c7854f66cc0f9008cb63df4c28ca)) 22 | 23 | # [0.8.0](https://github.com/newrelic/nr1-network-telemetry/compare/v0.7.0...v0.8.0) (2021-11-09) 24 | 25 | 26 | ### Features 27 | 28 | * reintroduce nvmrc and upgrade node ([51c5177](https://github.com/newrelic/nr1-network-telemetry/commit/51c51770c013558b1d012a7202728392e3a36504)) 29 | 30 | # [0.7.0](https://github.com/newrelic/nr1-network-telemetry/compare/v0.6.0...v0.7.0) (2021-11-05) 31 | 32 | 33 | ### Features 34 | 35 | * add catalog keywords and categoryTerms ([7dc4f21](https://github.com/newrelic/nr1-network-telemetry/commit/7dc4f21a6d3499d44c843b7262c07e562539e5ca)) 36 | 37 | # [0.6.0](https://github.com/newrelic/nr1-network-telemetry/compare/v0.5.3...v0.6.0) (2021-10-04) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * eslint run check ([dbcbb8e](https://github.com/newrelic/nr1-network-telemetry/commit/dbcbb8eb5f2e0b854d80b57cdd475f4a291f17a6)) 43 | * husky node version error ([a24354e](https://github.com/newrelic/nr1-network-telemetry/commit/a24354e41fb3029bafc021742ddc41a7a6d4044c)) 44 | * upgrading semantic to node 14 ([0263e05](https://github.com/newrelic/nr1-network-telemetry/commit/0263e059be976a204bd0c3412ff03e6e263cac68)) 45 | 46 | 47 | ### Features 48 | 49 | * update semantic release with node ([e49a784](https://github.com/newrelic/nr1-network-telemetry/commit/e49a784f1d85fddc4c8729930ab36eaa37eeadda)) 50 | 51 | ## [0.5.3](https://github.com/newrelic/nr1-network-telemetry/compare/v0.5.2...v0.5.3) (2021-02-26) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * removed code of conduct ([cc34f1f](https://github.com/newrelic/nr1-network-telemetry/commit/cc34f1f3eee4dd58af7dc063fb50c197127ae825)) 57 | 58 | ## [0.5.2](https://github.com/newrelic/nr1-network-telemetry/compare/v0.5.1...v0.5.2) (2021-02-01) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * Remove deprecated sizeType prop from nr1 Icon ([08213b9](https://github.com/newrelic/nr1-network-telemetry/commit/08213b9cd449d9b1f377fe5a0c25ef8ab93c03ea)) 64 | 65 | ## [0.5.1](https://github.com/newrelic/nr1-network-telemetry/compare/v0.5.0...v0.5.1) (2020-07-24) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * update screenshots ([dec3327](https://github.com/newrelic/nr1-network-telemetry/commit/dec3327cc6a4c141d9a9fb9b294f5f89d8025236)) 71 | 72 | # [0.5.0](https://github.com/newrelic/nr1-network-telemetry/compare/v0.4.1...v0.5.0) (2020-07-21) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * added empty state to the dropdown list & main view, updated readme ([ea2238c](https://github.com/newrelic/nr1-network-telemetry/commit/ea2238cd29d05fef29cc07f78d3e06ea07ec215b)) 78 | * adjusted pipeline to not crash ([2488281](https://github.com/newrelic/nr1-network-telemetry/commit/248828112c34123de09ba3cdf188c72c9d0a37c6)) 79 | * fixed incorrect error info ([4702138](https://github.com/newrelic/nr1-network-telemetry/commit/470213863c45b1e01852229fe91c5925240e8236)) 80 | * fixed pretteir setting and fixed files styling ([1e602d1](https://github.com/newrelic/nr1-network-telemetry/commit/1e602d1ac50bde6260b0f628982a2e1affd2f5be)) 81 | * linter fixes ([67f28f9](https://github.com/newrelic/nr1-network-telemetry/commit/67f28f9acb50afc067e14c15fc4c69cd7db15642)) 82 | * prettier file name fixed ([b336db5](https://github.com/newrelic/nr1-network-telemetry/commit/b336db5e68766bbae5c8647221c6dac2f6f78708)) 83 | * resolved conflicts with develop ([d70bc38](https://github.com/newrelic/nr1-network-telemetry/commit/d70bc38053cc256f98634d631f4497a4838bf269)) 84 | * third party notice commits from GH Actions ([#43](https://github.com/newrelic/nr1-network-telemetry/issues/43)) ([a57c63f](https://github.com/newrelic/nr1-network-telemetry/commit/a57c63fb1ee933cbf3c669498138b59a38e36c48)) 85 | * updating to latest version of semantic release plugins ([#45](https://github.com/newrelic/nr1-network-telemetry/issues/45)) ([070e5cb](https://github.com/newrelic/nr1-network-telemetry/commit/070e5cb4898b4aac8ea8df79a9a37ec5ab3aa229)) 86 | 87 | 88 | ### Features 89 | 90 | * added possibility to pause refreshing ([0d855d9](https://github.com/newrelic/nr1-network-telemetry/commit/0d855d91ee2e62336b969a578b3466cd97bc3045)) 91 | * adjusting main menu functionalities ([2f23fee](https://github.com/newrelic/nr1-network-telemetry/commit/2f23feed229149ffd339e6eb06d309f72f7d5163)) 92 | * created separated component to handle main menu ([3ce793a](https://github.com/newrelic/nr1-network-telemetry/commit/3ce793af738ee2dc986f55cd7b05ba03ca500de3)) 93 | * final adjustments, refactored main view, MainMenu component stable ([73deb80](https://github.com/newrelic/nr1-network-telemetry/commit/73deb80d3aa88b2eeea7486e7def000a3eaf0292)) 94 | * first steps of code refactor ([0452148](https://github.com/newrelic/nr1-network-telemetry/commit/04521489c29d0cd57ca941be224e3025c079d9c0)) 95 | * refactored views, passed data correctly ([2c42b79](https://github.com/newrelic/nr1-network-telemetry/commit/2c42b79cb81cfa980d065e42233331fc4feb48ae)) 96 | 97 | ## [0.4.1](https://github.com/newrelic/nr1-network-telemetry/compare/v0.4.0...v0.4.1) (2020-04-01) 98 | 99 | 100 | ### Bug Fixes 101 | 102 | * nerdpack ga updates ([#36](https://github.com/newrelic/nr1-network-telemetry/issues/36)) ([dd694f5](https://github.com/newrelic/nr1-network-telemetry/commit/dd694f5ee4543ed805a2802a7fbc3d92c837dba7)), closes [#35](https://github.com/newrelic/nr1-network-telemetry/issues/35) 103 | 104 | # [0.4.0](https://github.com/newrelic/nr1-network-telemetry/compare/v0.3.0...v0.4.0) (2020-03-09) 105 | 106 | 107 | ### Features 108 | 109 | * **catalog:** add catalog metadata files ([#32](https://github.com/newrelic/nr1-network-telemetry/issues/32)) ([32620ca](https://github.com/newrelic/nr1-network-telemetry/commit/32620ca78b652035b632cc4d791a673a9e4f4fdf)), closes [newrelic/nr1-network-telemetry#31](https://github.com/newrelic/nr1-network-telemetry/issues/31) 110 | 111 | # [0.3.0](https://github.com/newrelic/nr1-network-telemetry/compare/v0.2.0...v0.3.0) (2020-01-30) 112 | 113 | 114 | ### Features 115 | 116 | * limit width of src in network summary table ([8525e9b](https://github.com/newrelic/nr1-network-telemetry/commit/8525e9bbcec179c93cc7b7a562b02561c08baa3a)), closes [#10](https://github.com/newrelic/nr1-network-telemetry/issues/10) 117 | 118 | # [0.2.0](https://github.com/newrelic/nr1-network-telemetry/compare/v0.1.3...v0.2.0) (2019-11-15) 119 | 120 | 121 | ### Bug Fixes 122 | 123 | * address some of the lint issues ([f8cd37f](https://github.com/newrelic/nr1-network-telemetry/commit/f8cd37f7298a4e7004fc0ea49934504a75d4d0b2)) 124 | * linting cleanup ([6d63dae](https://github.com/newrelic/nr1-network-telemetry/commit/6d63dae36128697567cb09610d47370306d0c8ec)) 125 | * Remove hardcoded uuid ([2ee0b84](https://github.com/newrelic/nr1-network-telemetry/commit/2ee0b84cd6d1333beb4b8ce78fe7d0ac0ad131f8)) 126 | 127 | 128 | ### Features 129 | 130 | * drill down from ip address to host ([f0c8451](https://github.com/newrelic/nr1-network-telemetry/commit/f0c8451e3e158805c8282bce11119e5824d6a19b)) 131 | * show toast if we cant find entity guid ([6ca751a](https://github.com/newrelic/nr1-network-telemetry/commit/6ca751af73e9d57a221777bcbe9998a556746c77)) 132 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/nr1-network-telemetry/f9c88494bdb67987763d50514825aabba6344167/icon.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: ['**/*.js?(x)'], 3 | coveragePathIgnorePatterns: ['index.js', 'test'], 4 | moduleNameMapper: { 5 | '\\.(scss|css)$': '/test/utils/mocks/styleMock.jsx', 6 | '^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 7 | 'identity-obj-proxy', 8 | '^shared/(.*)$': '/lib/$1', 9 | '^testUtils/(.+)$': '/test/utils/$1', 10 | }, 11 | // Ignore npm caching to avoid problems with jest and chalk throwing errors 12 | // when running in grand central. Fix grabbed from this github issue: 13 | // https://github.com/facebook/jest/issues/4682#issuecomment-352899677 14 | modulePathIgnorePatterns: ['npm-cache', '.npm'], 15 | rootDir: '.', 16 | setupFiles: ['/test/utils/setupTests.jsx'], 17 | testMatch: ['/**/*.test.js?(x)'], 18 | testPathIgnorePatterns: ['/node_modules/', '/__mocks__/', '.eslintrc.js'], 19 | }; 20 | -------------------------------------------------------------------------------- /launchers/network-telemetry-launcher/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/nr1-network-telemetry/f9c88494bdb67987763d50514825aabba6344167/launchers/network-telemetry-launcher/icon.png -------------------------------------------------------------------------------- /launchers/network-telemetry-launcher/nr1.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaType": "LAUNCHER", 3 | "id": "network-telemetry-launcher", 4 | "description": "Network Telemetry", 5 | "displayName": "Network Telemetry", 6 | "rootNerdletId": "network-telemetry-overview" 7 | } 8 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/MainMenu.jsx: -------------------------------------------------------------------------------- 1 | import { BlockText, Button, Checkbox, Radio, RadioGroup, nerdlet } from 'nr1'; 2 | import { 3 | DATA_SOURCES, 4 | INTERVAL_SECONDS_DEFAULT, 5 | INTERVAL_SECONDS_MAX, 6 | INTERVAL_SECONDS_MIN, 7 | NRQL_QUERY_LIMIT_DEFAULT, 8 | NRQL_QUERY_LIMIT_MAX, 9 | NRQL_QUERY_LIMIT_MIN, 10 | } from './constants'; 11 | import { AccountDropdown } from '../../src/components/account-dropdown'; 12 | import InputRange from 'react-input-range'; 13 | import PropTypes from 'prop-types'; 14 | import React from 'react'; 15 | import debounce from 'lodash/debounce'; 16 | 17 | export default class MainMenu extends React.Component { 18 | static propTypes = { 19 | nerdletUrlState: PropTypes.object, 20 | onAccountChange: PropTypes.func, 21 | onAccountsLoaded: PropTypes.func, 22 | }; 23 | 24 | constructor(props) { 25 | super(); 26 | 27 | const intervalSeconds = props.nerdletUrlState.intervalSeconds || INTERVAL_SECONDS_DEFAULT; 28 | 29 | this.state = { 30 | intervalEnabled: true, 31 | intervalSlider: intervalSeconds, 32 | isLoading: true, 33 | }; 34 | } 35 | 36 | handleDataSourceChange = (evt, value) => { 37 | const dataSource = parseInt(value, 10); 38 | nerdlet.setUrlState({ dataSource }); 39 | }; 40 | 41 | toggleInterval = () => { 42 | const { intervalEnabled } = this.state; 43 | 44 | if (intervalEnabled) { 45 | nerdlet.setUrlState({ intervalSeconds: 0 }); 46 | } else { 47 | nerdlet.setUrlState({ intervalSeconds: INTERVAL_SECONDS_DEFAULT }); 48 | } 49 | this.setState({ intervalEnabled: !intervalEnabled }); 50 | }; 51 | 52 | handleIntervalSecondsChange = debounce(value => { 53 | const intervalSeconds = value || INTERVAL_SECONDS_DEFAULT; 54 | 55 | if (intervalSeconds >= INTERVAL_SECONDS_MIN) { 56 | nerdlet.setUrlState({ intervalSeconds }); 57 | } 58 | }, 500); 59 | 60 | handleAccountChange = async account => { 61 | if (account) { 62 | this.props.onAccountChange(account); 63 | } 64 | }; 65 | 66 | handleLimitChange(evt, value) { 67 | const queryLimit = parseInt(value, 10); 68 | 69 | if (queryLimit >= NRQL_QUERY_LIMIT_MIN && queryLimit <= NRQL_QUERY_LIMIT_MAX) { 70 | nerdlet.setUrlState({ queryLimit }); 71 | } 72 | } 73 | 74 | handleHideLabelsChange(evt) { 75 | const hideLabels = ((evt || {}).target || {}).checked || false; 76 | 77 | nerdlet.setUrlState({ hideLabels }); 78 | } 79 | 80 | render() { 81 | const { 82 | dataSource = 0, 83 | queryLimit = NRQL_QUERY_LIMIT_DEFAULT, 84 | hideLabels = false, 85 | } = this.props.nerdletUrlState; 86 | const { nerdletUrlState, onAccountsLoaded } = this.props; 87 | const { intervalSlider, intervalEnabled } = this.state; 88 | 89 | return ( 90 |
91 | 92 | Account 93 | 94 | 101 | 102 | Source 103 | 104 | 105 | {DATA_SOURCES.map((v, i) => ( 106 | 107 | ))} 108 | 109 |
110 | 111 | 117 | 118 | 119 | Limit results to about... 120 | 121 | 122 | 123 | 124 | 125 | 126 |
127 | {intervalEnabled && ( 128 | <> 129 | 130 | Refresh rate: 131 | 132 |
133 |
134 | `${value}s`} 136 | maxValue={INTERVAL_SECONDS_MAX} 137 | minValue={INTERVAL_SECONDS_MIN} 138 | onChange={intervalSlider => this.setState({ intervalSlider })} 139 | onChangeComplete={this.handleIntervalSecondsChange} 140 | step={1} 141 | value={intervalSlider} 142 | /> 143 |
144 | 145 | )} 146 | 147 | 154 |
155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/common.jsx: -------------------------------------------------------------------------------- 1 | import { BlockText, Icon } from 'nr1'; 2 | import React from 'react'; 3 | 4 | export const renderDeviceHeader = (deviceName, deviceType) => { 5 | return ( 6 |
7 | 8 |
9 | {deviceName || 'All Devices'} 10 | 11 | {deviceType || 'Device'} 12 | 13 |
14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/constants.js: -------------------------------------------------------------------------------- 1 | import Ipfix from './ipfix'; 2 | import Sflow from './sflow'; 3 | 4 | /* 5 | * Constants 6 | */ 7 | export const SUB_MENU_HEIGHT = 45; 8 | 9 | export const INTERVAL_SECONDS_MIN = 3; 10 | export const INTERVAL_SECONDS_MAX = 60; 11 | export const INTERVAL_SECONDS_DEFAULT = 30; 12 | 13 | export const BLURRED_LINK_OPACITY = 0.3; 14 | export const FOCUSED_LINK_OPACITY = 0.8; 15 | export const COLOR_END = '#FFC400'; 16 | export const COLOR_START = '#3ED2F2'; 17 | export const COLORS = [ 18 | '#11A893', 19 | '#00B3D7', 20 | '#FFC400', 21 | '#A45AC1', 22 | '#83CB4E', 23 | '#FA6E37', 24 | '#C40685', 25 | 26 | '#4ACAB7', 27 | '#3ED2F2', 28 | '#FFDD78', 29 | '#C07DDB', 30 | '#A2E572', 31 | '#FF9269', 32 | '#E550B0', 33 | 34 | '#0E7365', 35 | '#0189A4', 36 | '#CE9E00', 37 | '#79428E', 38 | '#63973A', 39 | '#C6562C', 40 | '#910662', 41 | ]; 42 | 43 | export const NRQL_QUERY_LIMIT_DEFAULT = 50; 44 | export const NRQL_QUERY_LIMIT_MIN = 1; 45 | export const NRQL_QUERY_LIMIT_MAX = 100; 46 | 47 | export const NRQL_IPFIX_WHERE_NO_PRIVATE_ASN = 48 | ' WHERE (bgpSourceAsNumber > 1 AND bgpSourceAsNumber < 64495)' + 49 | ' OR (bgpSourceAsNumber > 65534 AND bgpSourceAsNumber < 4200000000)' + 50 | ' OR (bgpSourceAsNumber > 4294967294)'; 51 | 52 | export const DATA_SOURCES = [ 53 | { component: Ipfix, eventType: 'ipfix', name: 'ipfix' }, 54 | { component: Sflow, eventType: 'sflow', name: 'sflow' }, 55 | ]; 56 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/fetch.js: -------------------------------------------------------------------------------- 1 | import { COLORS } from './constants'; 2 | import { fetchNrqlResults } from '../../src/lib/nrql'; 3 | 4 | /* 5 | * Fetch some node /link stuff via NRQL 6 | * should have a better name... 7 | */ 8 | export const fetchRelationshipFacets = async (accountId, nrqlQuery) => { 9 | const nodes = []; 10 | const links = []; 11 | 12 | if (accountId && nrqlQuery) { 13 | const results = await fetchNrqlResults(accountId, nrqlQuery); 14 | 15 | results.forEach(row => { 16 | // Collect nodes 17 | const ids = (row.facet || []).map(f => { 18 | const id = nodes.findIndex(node => node.name === f); 19 | if (id < 0) 20 | return ( 21 | nodes.push({ 22 | color: COLORS[nodes.length % COLORS.length], 23 | name: f, 24 | value: 0, 25 | }) - 1 26 | ); 27 | 28 | return id; 29 | }); 30 | 31 | const value = row.value; 32 | // Sum all from this source 33 | nodes[ids[0]].value += value; 34 | 35 | // Update existing links (0 => 1 => 2 etc) 36 | for (let x = 0; x < ids.length - 1; x++) { 37 | const sa = links.findIndex(link => link.source === ids[x] && link.target === ids[x + 1]); 38 | if (sa >= 0) { 39 | links[sa].value += value; 40 | } else { 41 | links.push({ 42 | color: nodes[ids[0]].color, 43 | source: ids[x], 44 | sourceId: ids[0], 45 | target: ids[x + 1], 46 | value, 47 | }); 48 | } 49 | } 50 | }); 51 | } 52 | 53 | return { links, nodes }; 54 | }; 55 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/index.jsx: -------------------------------------------------------------------------------- 1 | import NetworkTelemetryNerdlet from './network-telemetry-nerdlet'; 2 | import React from 'react'; 3 | 4 | export default class Wrapper extends React.PureComponent { 5 | render() { 6 | return ; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/ip-address.jsx: -------------------------------------------------------------------------------- 1 | import { Toast, navigation } from 'nr1'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import findRelatedAccountsWith from '../../src/lib/find-related-account-with'; 6 | import nrdbQuery from '../../src/lib/nrdb-query'; 7 | 8 | /** 9 | * render an ip address that on click will attempt to find the 10 | * host associated with it. Look at NetworkSample event data 11 | * and search across all accounts. 12 | */ 13 | export default class IpAddress extends React.Component { 14 | static propTypes = { 15 | value: PropTypes.string, 16 | }; 17 | 18 | constructor(props) { 19 | super(props); 20 | 21 | this.onClick = this.onClick.bind(this); 22 | } 23 | 24 | async onClick() { 25 | const ipAddress = this.props.value; 26 | const { searching } = this.state || {}; 27 | if (!ipAddress || searching) return; 28 | 29 | this.setState({ searching: true }); 30 | const where = `ipV4Address LIKE '${ipAddress}/%'`; 31 | const notFoundToast = { 32 | description: `Could not find ${ipAddress} on any monitored hosts.`, 33 | title: 'IP Address Not Found', 34 | type: Toast.TYPE.NORMAL, 35 | }; 36 | 37 | const accounts = await findRelatedAccountsWith({ 38 | eventType: 'NetworkSample', 39 | where, 40 | }); 41 | if (accounts.length === 0) { 42 | Toast.showToast(notFoundToast); 43 | } else { 44 | const account = accounts[0]; 45 | const nrql = `SELECT latest(entityGuid) as entityGuid FROM NetworkSample WHERE ${where} SINCE 2 minutes ago`; 46 | const results = await nrdbQuery(account.id, nrql); 47 | if (results && results.length > 0 && results[0].entityGuid) { 48 | navigation.openStackedEntity(results[0].entityGuid); 49 | } else { 50 | Toast.showToast(notFoundToast); 51 | } 52 | } 53 | this.setState({ searching: false }); 54 | } 55 | 56 | render() { 57 | const { searching } = this.state || {}; 58 | const className = searching ? 'ip-address-searching' : 'ip-address'; 59 | return ( 60 |
67 | {this.props.value || '(unknown)'} 68 |
69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/ipfix-detail.jsx: -------------------------------------------------------------------------------- 1 | import { ChartGroup, HeadingText, LineChart, SparklineChart } from 'nr1'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | import { renderDeviceHeader } from './common'; 6 | 7 | export default class IpfixDetail extends React.Component { 8 | static propTypes = { 9 | accountId: PropTypes.number.isRequired, 10 | filter: PropTypes.string, 11 | hideLabels: PropTypes.bool, 12 | name: PropTypes.string, 13 | }; 14 | 15 | /* 16 | * Main render 17 | */ 18 | render() { 19 | const { accountId, filter, hideLabels, name } = this.props; 20 | 21 | const displayName = hideLabels ? '(redacted)' : name; 22 | 23 | // Kill facets when labels are hidden 24 | const ChartComponent = hideLabels ? SparklineChart : LineChart; 25 | 26 | const throughputQuery = 27 | 'FROM ipfix' + 28 | " SELECT sum(octetDeltaCount * 64000) as 'throughput'" + 29 | (filter || '') + 30 | ' TIMESERIES'; 31 | 32 | const destQuery = 33 | 'FROM ipfix' + 34 | " SELECT sum(octetDeltaCount * 64000) as 'throughput'" + 35 | (filter || '') + 36 | ' FACET destinationIPv4Address ' + 37 | ' TIMESERIES'; 38 | 39 | const protocolQuery = 40 | 'FROM ipfix' + 41 | " SELECT count(*) as 'flows'" + 42 | (filter || '') + 43 | ' FACET cases(' + 44 | " WHERE protocolIdentifier = 1 as 'ICMP', " + 45 | " WHERE protocolIdentifier = 6 as 'TCP'," + 46 | " WHERE protocolIdentifier = 17 as 'UDP'," + 47 | " WHERE protocolIdentifier IS NOT NULL as 'other')" + 48 | ' TIMESERIES'; 49 | 50 | return ( 51 |
52 | 53 | {renderDeviceHeader(displayName, 'Network Entity')} 54 | Total Throughput 55 | 60 | 61 | Throughput by Destination IP 62 | 67 | 68 | Flows by Protocol 69 | 74 | 75 |
76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/ipfix.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | BLURRED_LINK_OPACITY, 3 | FOCUSED_LINK_OPACITY, 4 | INTERVAL_SECONDS_DEFAULT, 5 | INTERVAL_SECONDS_MIN, 6 | NRQL_IPFIX_WHERE_NO_PRIVATE_ASN, 7 | NRQL_QUERY_LIMIT_DEFAULT, 8 | SUB_MENU_HEIGHT, 9 | } from './constants'; 10 | 11 | import { 12 | BlockText, 13 | Checkbox, 14 | Grid, 15 | GridItem, 16 | Modal, 17 | Radio, 18 | RadioGroup, 19 | Spinner, 20 | Stack, 21 | StackItem, 22 | } from 'nr1'; 23 | 24 | import IpfixDetail from './ipfix-detail'; 25 | import NetworkSummary from './network-summary'; 26 | import PropTypes from 'prop-types'; 27 | import React from 'react'; 28 | import { Sankey } from 'react-vis'; 29 | import { fetchRelationshipFacets } from './fetch'; 30 | 31 | export default class Ipfix extends React.Component { 32 | static propTypes = { 33 | account: PropTypes.object.isRequired, 34 | height: PropTypes.number, 35 | hideLabels: PropTypes.bool, 36 | intervalSeconds: PropTypes.number, 37 | queryLimit: PropTypes.number, 38 | width: PropTypes.number, 39 | }; 40 | 41 | static defaultProps = { 42 | height: 650, 43 | hideLabels: false, 44 | intervalSeconds: INTERVAL_SECONDS_DEFAULT, 45 | queryLimit: NRQL_QUERY_LIMIT_DEFAULT, 46 | width: 700, 47 | }; 48 | 49 | constructor(props) { 50 | super(props); 51 | 52 | const { height, width } = this.props; 53 | 54 | this.state = { 55 | activeLink: null, 56 | detailData: null, 57 | detailHidden: true, 58 | filterPrivateAsns: true, 59 | height: height - SUB_MENU_HEIGHT - 10, 60 | isLoading: true, 61 | links: [], 62 | nodes: [], 63 | peerBy: 'peerName', 64 | width: width, 65 | }; 66 | 67 | this.handleDetailClose = this.handleDetailClose.bind(this); 68 | this.handleFilterPrivateAsnsChange = this.handleFilterPrivateAsnsChange.bind(this); 69 | this.handlePeerByChange = this.handlePeerByChange.bind(this); 70 | this.handleSankeyLinkClick = this.handleSankeyLinkClick.bind(this); 71 | } 72 | 73 | componentDidMount() { 74 | this.startTimer(); 75 | } 76 | 77 | componentDidUpdate(prevProps, prevState) { 78 | const { account, intervalSeconds, queryLimit } = this.props; 79 | const { peerBy } = this.state; 80 | 81 | if ( 82 | account.id !== prevProps.account.id || 83 | intervalSeconds !== prevProps.intervalSeconds || 84 | queryLimit !== prevProps.queryLimit || 85 | peerBy !== prevState.peerBy 86 | ) { 87 | this.resetTimer(); 88 | } 89 | 90 | if (this.graphContainer && this.graphContainer.clientWidth !== prevState.width) { 91 | const width = this.graphContainer.clientWidth; 92 | this.setState({ width }); 93 | } 94 | } 95 | 96 | componentWillUnmount() { 97 | this.stopTimer(); 98 | } 99 | 100 | startTimer() { 101 | const { intervalSeconds } = this.props || INTERVAL_SECONDS_DEFAULT; 102 | 103 | if (intervalSeconds >= INTERVAL_SECONDS_MIN) { 104 | this.fetchIpfixData(); 105 | 106 | this.refresh = setInterval(async () => { 107 | this.fetchIpfixData(); 108 | }, intervalSeconds * 1000); 109 | } 110 | } 111 | 112 | stopTimer() { 113 | if (this.refresh) clearInterval(this.refresh); 114 | } 115 | 116 | async resetTimer() { 117 | await this.setState({ isLoading: true }); 118 | this.stopTimer(); 119 | this.startTimer(); 120 | } 121 | 122 | handleSankeyLinkClick(detailData, evt) { 123 | this.setState({ detailData, detailHidden: false }); 124 | } 125 | 126 | handleDetailClose() { 127 | this.setState({ detailHidden: true }); 128 | } 129 | 130 | async handlePeerByChange(evt, peerBy) { 131 | if (peerBy) { 132 | await this.setState({ peerBy }); 133 | this.fetchIpfixData(); 134 | } 135 | } 136 | 137 | async handleFilterPrivateAsnsChange(evt) { 138 | const filterPrivateAsns = ((evt || {}).target || {}).checked; 139 | 140 | await this.setState({ filterPrivateAsns }); 141 | this.fetchIpfixData(); 142 | } 143 | 144 | createNrqlQuery() { 145 | const { intervalSeconds, queryLimit } = this.props; 146 | const { filterPrivateAsns, peerBy } = this.state; 147 | 148 | return ( 149 | 'FROM ipfix' + 150 | " SELECT sum(octetDeltaCount * 64000) as 'value'" + 151 | (filterPrivateAsns ? NRQL_IPFIX_WHERE_NO_PRIVATE_ASN : '') + 152 | ' FACET ' + 153 | peerBy + 154 | ', agent, destinationIPv4Address' + 155 | ' SINCE ' + 156 | intervalSeconds + 157 | ' seconds ago' + 158 | ' LIMIT ' + 159 | queryLimit 160 | ); 161 | } 162 | 163 | async fetchIpfixData() { 164 | if (!this.props) return; 165 | const account = this.props.account; 166 | 167 | if (!account || !account.id) return; 168 | 169 | const { nodes, links } = await fetchRelationshipFacets(account.id, this.createNrqlQuery()); 170 | 171 | this.setState({ 172 | isLoading: false, 173 | links, 174 | nodes, 175 | }); 176 | } 177 | 178 | /* 179 | * Render detailed modal 180 | */ 181 | renderDetailCard() { 182 | const account = this.props.account || {}; 183 | const { hideLabels } = this.props; 184 | const { detailData, detailHidden, filterPrivateAsns, nodes, peerBy } = this.state; 185 | 186 | if (!account || !detailData || detailHidden) return; 187 | 188 | let peerName = (nodes[detailData.sourceId] || {}).name || ''; 189 | let filter = 190 | (filterPrivateAsns ? `${NRQL_IPFIX_WHERE_NO_PRIVATE_ASN} AND ` : 'WHERE ') + peerBy + ' = '; 191 | if (peerBy === 'bgpSourceAsNumber') { 192 | filter += peerName; 193 | } else { 194 | filter += "'" + peerName + "'"; 195 | } 196 | 197 | if ((detailData.source || {}).depth === 1) { 198 | filter += " AND destinationIPv4Address = '" + detailData.target.name + "'"; 199 | peerName += ' to ' + detailData.target.name; 200 | } 201 | 202 | return ( 203 | 211 | ); 212 | } 213 | 214 | renderSubMenu() { 215 | const { filterPrivateAsns, peerBy } = this.state; 216 | 217 | return ( 218 |
219 |
220 | 221 | Show peers by... 222 | 223 | 228 | 229 | 230 | 231 |
232 | 233 |
234 | 240 |
241 |
242 | ); 243 | } 244 | 245 | /* 246 | * Main render 247 | */ 248 | render() { 249 | const { hideLabels } = this.props; 250 | const { activeLink, height, links, nodes, isLoading, width } = this.state; 251 | 252 | // Add link highlighting 253 | const renderLinks = links.map((link, linkIndex) => { 254 | let opacity = BLURRED_LINK_OPACITY; 255 | 256 | if (activeLink) { 257 | // I'm the hovered link 258 | if (linkIndex === activeLink.index) { 259 | opacity = FOCUSED_LINK_OPACITY; 260 | } else { 261 | // let's recurse 262 | const myLinks = [ 263 | ...((activeLink.source || {}).targetLinks || []), 264 | ...((activeLink.target || {}).sourceLinks || []), 265 | ]; 266 | if (myLinks) { 267 | myLinks.forEach(t => { 268 | if (t.index === linkIndex && t.sourceId === activeLink.sourceId) 269 | opacity = FOCUSED_LINK_OPACITY; 270 | }); 271 | } 272 | } 273 | } 274 | 275 | return { ...link, opacity }; 276 | }); 277 | 278 | const renderNodes = hideLabels ? nodes.map((n, idx) => ({ ...n, name: `${idx}` })) : nodes; 279 | 280 | return ( 281 |
282 | {this.renderDetailCard()} 283 | 284 | 285 | 291 | 292 |
293 | {this.renderSubMenu()} 294 |
295 |
296 | 297 |
{ 300 | this.graphContainer = graphContainer; 301 | }} 302 | > 303 | {isLoading ? ( 304 | 305 | ) : nodes.length < 1 ? ( 306 |
No results found
307 | ) : ( 308 |
309 |
316 |
323 | Source 324 |
325 |
332 | Router 333 |
334 |
341 | Destination 342 |
343 |
344 | this.setState({ activeLink: null })} 350 | onLinkMouseOver={node => this.setState({ activeLink: node })} 351 | width={width} 352 | /> 353 |
354 | )} 355 |
356 |
357 |
358 |
359 | 360 | 367 | 368 |
369 |
370 | ); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/network-summary.jsx: -------------------------------------------------------------------------------- 1 | import { bitsToSize, intToSize } from '../../src/lib/bytes-to-size'; 2 | 3 | import IpAddress from './ip-address'; 4 | import PropTypes from 'prop-types'; 5 | import React from 'react'; 6 | import { Table } from 'semantic-ui-react'; 7 | import { renderDeviceHeader } from './common'; 8 | 9 | export default class NetworkSummary extends React.Component { 10 | static propTypes = { 11 | columns: PropTypes.array, 12 | data: PropTypes.array, 13 | deviceName: PropTypes.string, 14 | deviceType: PropTypes.string, 15 | height: PropTypes.number, 16 | hideLabels: PropTypes.bool, 17 | }; 18 | 19 | static defaultProps = { 20 | columns: [ 21 | { align: 'center', data: 'color', label: null }, 22 | { align: 'left', data: 'name', label: 'Source' }, 23 | { align: 'right', data: 'value', label: 'Throughput' }, 24 | ], 25 | data: [], 26 | deviceName: 'All Devices', 27 | deviceType: 'Device', 28 | height: '100%', 29 | hideLabels: false, 30 | selectedNodeId: null, 31 | }; 32 | 33 | /* 34 | * Main render 35 | */ 36 | render() { 37 | const { columns, data, deviceName, deviceType, height, hideLabels } = this.props; 38 | 39 | const rows = data 40 | .filter(a => a.value > 0) 41 | .sort((a, b) => (a.value < b.value ? 1 : -1)) 42 | .reduce((row, node, idx) => { 43 | row.push( 44 | 45 | {columns.map((c, i) => { 46 | switch (c.data) { 47 | case 'color': 48 | return ( 49 | 50 |
51 | 52 | ); 53 | case 'value': 54 | return ( 55 | 56 | {c.type && c.type === 'count' 57 | ? intToSize(node.value, hideLabels) 58 | : bitsToSize(node.value, hideLabels)} 59 | 60 | ); 61 | default: 62 | return ( 63 | 64 | 65 | 66 | ); 67 | } 68 | })} 69 | 70 | ); 71 | 72 | return row; 73 | }, []); 74 | 75 | return ( 76 |
77 | {renderDeviceHeader(deviceName, deviceType)} 78 |
79 | 80 | 81 | 82 | {columns.map((c, i) => ( 83 | {c.label || ' '} 84 | ))} 85 | 86 | 87 | {rows.map(row => row)} 88 |
89 |
90 |
91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/network-telemetry-nerdlet.jsx: -------------------------------------------------------------------------------- 1 | import { DATA_SOURCES, INTERVAL_SECONDS_DEFAULT, NRQL_QUERY_LIMIT_DEFAULT } from './constants'; 2 | import { 3 | Grid, 4 | GridItem, 5 | HeadingText, 6 | Icon, 7 | NerdletStateContext, 8 | PlatformStateContext, 9 | Spinner, 10 | } from 'nr1'; 11 | import { EmptyState } from '@newrelic/nr1-community'; 12 | import MainMenu from './MainMenu'; 13 | 14 | import React from 'react'; 15 | 16 | export default class NetworkTelemetryNerdlet extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | 20 | this.state = { 21 | account: {}, 22 | accountsList: [], 23 | isLoading: true, 24 | }; 25 | } 26 | 27 | handleAccountsLoaded = accountsList => { 28 | if (accountsList && accountsList.length > 0) { 29 | this.setState({ accountsList, isLoading: false }); 30 | } 31 | }; 32 | 33 | accountFilter(account) { 34 | return DATA_SOURCES.reduce((found, source) => { 35 | return found || account.reportingEventTypes.includes(source.eventType); 36 | }, false); 37 | } 38 | 39 | renderSelectAccountAlert = () => { 40 | return ( 41 |
42 | 43 | 47 | Please Select an Account 48 | 49 |
50 | ); 51 | }; 52 | 53 | renderDSComponent = () => { 54 | const { account } = this.state; 55 | return ( 56 | 57 | {platformUrlState => { 58 | const { timeRange } = platformUrlState; 59 | return ( 60 | 61 | {nerdletUrlState => { 62 | const { 63 | dataSource = 0, 64 | hideLabels = false, 65 | intervalSeconds, 66 | queryLimit, 67 | } = nerdletUrlState; 68 | const DsComponent = (DATA_SOURCES[dataSource] || {}).component; 69 | return ( 70 | 77 | ); 78 | }} 79 | 80 | ); 81 | }} 82 | 83 | ); 84 | }; 85 | 86 | renderAccountsListError = () => { 87 | return ( 88 | 95 | ); 96 | }; 97 | 98 | render() { 99 | const { account, isLoading, accountsList } = this.state; 100 | 101 | return ( 102 |
103 | 104 | 105 | 106 | {nerdletUrlState => ( 107 | this.setState({ account, isLoading: false })} 110 | onAccountsLoaded={this.handleAccountsLoaded} 111 | /> 112 | )} 113 | 114 | 115 | 116 |
117 | {isLoading && } 118 | {!account.id && accountsList.length > 0 && this.renderSelectAccountAlert()} 119 | {!account.id && 120 | accountsList.length === 0 && 121 | !isLoading && 122 | this.renderAccountsListError()} 123 | {!isLoading && account.id && this.renderDSComponent()} 124 |
125 |
126 |
127 |
128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/nr1.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaType": "NERDLET", 3 | "id": "network-telemetry-overview", 4 | "description": "Network Telemetry Overview", 5 | "displayName": "Network Telemetry" 6 | } 7 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/sflow.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | BLURRED_LINK_OPACITY, 3 | FOCUSED_LINK_OPACITY, 4 | INTERVAL_SECONDS_DEFAULT, 5 | INTERVAL_SECONDS_MIN, 6 | NRQL_QUERY_LIMIT_DEFAULT, 7 | SUB_MENU_HEIGHT, 8 | } from './constants'; 9 | import { BlockText, Grid, GridItem, Radio, RadioGroup, Spinner, Stack, StackItem } from 'nr1'; 10 | 11 | import ChordDiagram from 'react-chord-diagram'; 12 | import NetworkSummary from './network-summary'; 13 | import PropTypes from 'prop-types'; 14 | import React from 'react'; 15 | import { fetchRelationshipFacets } from './fetch'; 16 | import { timeRangeToNrql } from '../../src/components/time-range'; 17 | 18 | export default class Sflow extends React.Component { 19 | static propTypes = { 20 | account: PropTypes.object.isRequired, 21 | configRenderer: PropTypes.func, 22 | height: PropTypes.number, 23 | hideLabels: PropTypes.bool, 24 | queryLimit: PropTypes.number, 25 | summaryRenderer: PropTypes.func, 26 | timeRange: PropTypes.object.isRequired, 27 | width: PropTypes.number, 28 | }; 29 | 30 | static defaultProps = { 31 | height: 650, 32 | hideLabels: false, 33 | queryLimit: NRQL_QUERY_LIMIT_DEFAULT, 34 | width: 700, 35 | }; 36 | 37 | constructor(props) { 38 | super(props); 39 | 40 | const { height, width } = this.props; 41 | 42 | this.state = { 43 | height: height - SUB_MENU_HEIGHT - 10, 44 | isLoading: true, 45 | links: [], 46 | nodes: [], 47 | queryAttribute: 'throughput', 48 | selectedSourceId: -1, 49 | width: width, 50 | }; 51 | 52 | this.handleAttributeChange = this.handleAttributeChange.bind(this); 53 | this.handleChartGroupClick = this.handleChartGroupClick.bind(this); 54 | } 55 | 56 | componentDidMount() { 57 | this.startTimer(); 58 | } 59 | 60 | componentDidUpdate(prevProps, prevState) { 61 | const { account, queryLimit, timeRange } = this.props; 62 | const { queryAttribute } = this.state; 63 | 64 | if ( 65 | account !== prevProps.account || 66 | queryAttribute !== prevState.queryAttribute || 67 | queryLimit !== prevProps.queryLimit || 68 | timeRange !== prevProps.timeRange 69 | ) { 70 | this.resetTimer(); 71 | } 72 | 73 | if (this.graphContainer && this.graphContainer.clientWidth !== prevState.width) { 74 | const width = this.graphContainer.clientWidth; 75 | this.setState({ width }); 76 | } 77 | } 78 | 79 | componentWillUnmount() { 80 | this.stopTimer(); 81 | } 82 | 83 | /* 84 | * Timer 85 | */ 86 | startTimer() { 87 | const { intervalSeconds } = this.props || INTERVAL_SECONDS_DEFAULT; 88 | 89 | if (intervalSeconds >= INTERVAL_SECONDS_MIN) { 90 | // Fire right away, then schedule 91 | this.fetchData(); 92 | 93 | this.refresh = setInterval(async () => { 94 | this.fetchData(); 95 | }, intervalSeconds * 1000); 96 | } 97 | } 98 | 99 | stopTimer() { 100 | if (this.refresh) clearInterval(this.refresh); 101 | } 102 | 103 | async resetTimer() { 104 | await this.setState({ isLoading: true }); 105 | this.stopTimer(); 106 | this.startTimer(); 107 | } 108 | 109 | /* 110 | * fetch data 111 | */ 112 | async fetchData() { 113 | const { account } = this.props; 114 | 115 | if (!account || !account.id) return; 116 | 117 | const { nodes, links } = await fetchRelationshipFacets(account.id, this.createNrqlQuery()); 118 | 119 | this.setState({ 120 | isLoading: false, 121 | links, 122 | nodes, 123 | }); 124 | } 125 | 126 | createNrqlQuery() { 127 | const { queryLimit } = this.props; 128 | const { queryAttribute } = this.state; 129 | 130 | let attr = 'sum(scaledByteCount * 8)'; 131 | if (queryAttribute === 'count') { 132 | attr = 'count(*)'; 133 | } 134 | 135 | return ( 136 | 'FROM sflow' + 137 | ' SELECT ' + 138 | attr + 139 | " as 'value'" + 140 | ' FACET networkSourceAddress, networkDestinationAddress' + 141 | ' LIMIT ' + 142 | queryLimit + 143 | ' ' + 144 | timeRangeToNrql(this.props.timeRange) 145 | ); 146 | } 147 | 148 | handleAttributeChange(evt, attr) { 149 | if (attr === 'count' || attr === 'throughput') { 150 | this.setState({ queryAttribute: attr }); 151 | } 152 | } 153 | 154 | handleChartGroupClick(id) { 155 | const { selectedSourceId } = this.state; 156 | 157 | if (id === selectedSourceId || id === null) this.setState({ selectedSourceId: -1 }); 158 | else this.setState({ selectedSourceId: id }); 159 | } 160 | 161 | renderSubMenu() { 162 | const { queryAttribute } = this.state; 163 | 164 | return ( 165 |
166 |
167 | 168 | Show devices with... 169 | 170 | 175 | 176 | 177 | 178 |
179 |
180 | ); 181 | } 182 | 183 | render() { 184 | const { hideLabels } = this.props; 185 | const { isLoading, height, nodes, links, queryAttribute, selectedSourceId, width } = this.state; 186 | const outerRadius = Math.min(height, width) * 0.5 - 100; 187 | const innerRadius = outerRadius - 10; 188 | 189 | const entityCount = nodes.length; 190 | const matrix = []; 191 | 192 | // create a matrix of zeros 193 | for (let x = 0; x <= entityCount; x++) { 194 | matrix[x] = []; 195 | for (let y = 0; y <= entityCount; y++) { 196 | matrix[x][y] = 0; 197 | } 198 | } 199 | 200 | // Add the links 201 | links.forEach(link => (matrix[link.source][link.target] = link.value)); 202 | 203 | const summaryColumns = [ 204 | { align: 'center', data: 'color', label: null }, 205 | { align: 'left', data: 'source', label: 'source' }, 206 | { align: 'left', data: 'target', label: 'target' }, 207 | { align: 'right', data: 'value', label: queryAttribute }, 208 | ]; 209 | 210 | const summaryData = links 211 | .filter(l => 212 | selectedSourceId < 0 ? true : l.source === selectedSourceId || l.target === selectedSourceId 213 | ) 214 | .map(l => { 215 | const source = hideLabels ? l.source : nodes[l.source].name; 216 | const target = hideLabels ? l.target : nodes[l.target].name; 217 | 218 | return { ...l, source, target }; 219 | }); 220 | 221 | const deviceName = 222 | selectedSourceId >= 0 223 | ? hideLabels 224 | ? `${selectedSourceId}` 225 | : (nodes[selectedSourceId] || {}).name 226 | : null; 227 | 228 | return ( 229 |
230 | 231 | 232 | 238 | 239 |
240 | {this.renderSubMenu()} 241 |
242 |
243 | 244 |
{ 247 | this.graphContainer = graphContainer; 248 | }} 249 | > 250 | {isLoading ? ( 251 | 252 | ) : nodes.length < 1 ? ( 253 |
No results found
254 | ) : ( 255 | n.color)} 260 | groupLabels={nodes.map((n, idx) => (hideLabels ? idx : n.name))} 261 | groupOnClick={this.handleChartGroupClick} 262 | height={height} 263 | innerRadius={innerRadius} 264 | matrix={matrix} 265 | outerRadius={outerRadius} 266 | persistHoverOnClick={true} 267 | ribbonBlurOpacity={`${BLURRED_LINK_OPACITY}`} 268 | ribbonOnClick={this.handleChartGroupClick} 269 | ribbonOpacity={`${FOCUSED_LINK_OPACITY}`} 270 | strokeWidth={0} 271 | svgOnClick={() => { 272 | this.handleChartGroupClick(null); 273 | }} 274 | width={width} 275 | /> 276 | )} 277 |
278 |
279 |
280 |
281 | 282 | 289 | 290 |
291 |
292 | ); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /nerdlets/network-telemetry-overview/styles.scss: -------------------------------------------------------------------------------- 1 | @import url('//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css'); 2 | @import '~react-input-range/lib/css/index.css'; 3 | 4 | /* Interval Slider */ 5 | .interval-range { 6 | margin: 5px 5px 2em; 7 | } 8 | .input-range__label { 9 | color: #000e0e; 10 | } 11 | .input-range__track { 12 | background: #cecece; 13 | } 14 | .input-range__track--active { 15 | background: #0079bf; 16 | } 17 | .input-range__slider { 18 | background-color: #0079bf; 19 | border-color: #003555; 20 | } 21 | 22 | .main-container { 23 | -webkit-box-align: top; 24 | -webkit-justify-content: center; 25 | align-items: top; 26 | background-color: #ffffff; 27 | display: flex; 28 | height: 100%; 29 | justify-content: center; 30 | overflow: hidden; 31 | width: 100%; 32 | } 33 | 34 | .sub-menu { 35 | background-color: #ffffff; 36 | display: flex; 37 | height: 100%; 38 | overflow: hidden; 39 | padding: 5px; 40 | width: 100%; 41 | } 42 | 43 | .chord-chart { 44 | font-size: 10px; 45 | } 46 | 47 | .sankey-chart { 48 | font-size: 10px; 49 | } 50 | 51 | .fullheight { 52 | height: 100%; 53 | } 54 | 55 | .background { 56 | background-color: #f7f7f8; 57 | height: 100%; 58 | width: 100%; 59 | } 60 | 61 | .side-menu { 62 | background-color: #ffffff; 63 | height: 100%; 64 | padding: 5px; 65 | min-width: 190px; 66 | } 67 | 68 | .top-menu { 69 | display: flex; 70 | div { 71 | margin-right: 5px; 72 | } 73 | } 74 | 75 | .network-summary { 76 | margin-top: 8px !important; 77 | height: 100%; 78 | overflow-y: scroll; 79 | overflow-x: none; 80 | 81 | label { 82 | display: block; 83 | } 84 | 85 | .detail { 86 | margin: 5px; 87 | margin-left: 10px; 88 | } 89 | 90 | .color-circle { 91 | border-radius: 50%; 92 | display: inline-block; 93 | height: 6px; 94 | margin: 5px; 95 | width: 6px; 96 | } 97 | 98 | .ui.table { 99 | background-color: #ffffff; 100 | border-radius: 0; 101 | border-right-width: 0; 102 | border-left-width: 0; 103 | } 104 | } 105 | .network-summary__header { 106 | padding: 5px; 107 | background-color: #ffffff; 108 | 109 | display: flex; 110 | .ic-Icon { 111 | margin-top: 2px; 112 | height: 100%; 113 | } 114 | } 115 | .network-summary__header-name { 116 | margin: 0px 5px; 117 | 118 | p { 119 | font-size: 16px; 120 | margin: 0; 121 | } 122 | 123 | .sub-heading { 124 | color: #8e9494; 125 | font-size: 12px; 126 | } 127 | } 128 | 129 | .modal { 130 | height: 100%; 131 | } 132 | 133 | .side-info-chart { 134 | height: 200px; 135 | } 136 | 137 | .account-dropdown { 138 | font-size: 12px; 139 | margin-bottom: 10px; 140 | } 141 | 142 | .horizontal-radio-group { 143 | -webkit-box-align: center; 144 | align-items: center; 145 | display: flex; 146 | margin-bottom: 4px; 147 | 148 | :not(:last-child) { 149 | margin-bottom: 0 !important; 150 | } 151 | } 152 | 153 | label { 154 | color: #000e0e; 155 | font-size: 12px; 156 | line-height: 16px; 157 | position: relative; 158 | } 159 | 160 | .radio-option { 161 | -webkit-box-align: center; 162 | align-items: center; 163 | display: flex; 164 | margin-bottom: 4px; 165 | } 166 | 167 | .checkbox { 168 | margin-right: 5px; 169 | margin-bottom: 10px; 170 | -webkit-box-align: center; 171 | align-items: center; 172 | } 173 | 174 | .select-account { 175 | margin: 20px; 176 | } 177 | 178 | .ip-address { 179 | max-width: 200px; 180 | white-space: nowrap; 181 | overflow: hidden; 182 | text-overflow: ellipsis; 183 | } 184 | 185 | .ip-address:hover { 186 | cursor: pointer; 187 | } 188 | 189 | .ip-address-searching:hover { 190 | cursor: wait; 191 | } 192 | -------------------------------------------------------------------------------- /nr1.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaType": "NERDPACK", 3 | "id": "c03725a8-878f-408b-9853-6568e0ee087a", 4 | "displayName": "Network Telemetry", 5 | "description": "Real time IPfix and Sflow visualization of network data passing through your system." 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nr1-network-telemetry-nerdpack", 4 | "description": "Real time IPfix and Sflow visualization of network data passing through your system.", 5 | "version": "0.8.3", 6 | "scripts": { 7 | "checkversion": "check-node-version --node $(cat .nvmrc)", 8 | "format": "prettier --check \"**/*.js\" \"**/*.json\" \"**/*.jsx\" \"**/*.scss\" --ignore-path .eslintignore", 9 | "format:fix": "prettier --write \"**/*.js\" \"**/*.json\" \"**/*.jsx\" \"**/*.scss\" --ignore-path .eslintignore", 10 | "lint": "npm-run-all --parallel format lint:*", 11 | "lint:css": "stylelint 'nerdlets/**/*.scss' --syntax scss", 12 | "lint:css:fix": "npm run lint:css -- --fix", 13 | "lint:js": "eslint --ext js,jsx --ignore-path .eslintignore .", 14 | "lint:js:fix": "npm run lint:js -- --fix", 15 | "start": "nr1 nerdpack:serve", 16 | "test": "npm-run-all --sequential checkversion lint test:js", 17 | "test:js": "jest", 18 | "eslint-check": "eslint --ext js,jsx --ignore-path .eslintignore .", 19 | "eslint-fix": "npm run lint:js -- --fix" 20 | }, 21 | "husky": { 22 | "hooks": { 23 | "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS", 24 | "pre-push": "npm run test" 25 | } 26 | }, 27 | "bugs": { 28 | "email": "opensource@newrelic.com" 29 | }, 30 | "dependencies": { 31 | "@newrelic/nr1-community": "^1.2.0", 32 | "d3": "3.5.17", 33 | "lodash": "^4.17.20", 34 | "prop-types": "^15.6.2", 35 | "react-chord-diagram": "^1.5.0", 36 | "react-input-range": "^1.3.0", 37 | "react-vis": "^1.11.7", 38 | "semantic-ui-react": "^0.88.1" 39 | }, 40 | "browserslist": [ 41 | "last 2 versions", 42 | "not ie < 11", 43 | "not dead" 44 | ], 45 | "devDependencies": { 46 | "@babel/plugin-proposal-class-properties": "^7.10.4", 47 | "@babel/plugin-proposal-object-rest-spread": "^7.11.0", 48 | "@babel/plugin-transform-runtime": "^7.11.5", 49 | "@babel/preset-env": "^7.11.5", 50 | "@babel/preset-react": "^7.10.4", 51 | "@commitlint/cli": "^8.3.5", 52 | "@commitlint/config-conventional": "^8.1.0", 53 | "@newrelic/eslint-plugin-newrelic": "^0.3.1", 54 | "@semantic-release/changelog": "^5.0.1", 55 | "@semantic-release/git": "^9.0.0", 56 | "@typescript-eslint/eslint-plugin": "^2.34.0", 57 | "@typescript-eslint/experimental-utils": "^2.34.0", 58 | "@typescript-eslint/parser": "^2.34.0", 59 | "babel-core": "^6.26.3", 60 | "babel-eslint": "^10.0.3", 61 | "babel-jest": "^24.9.0", 62 | "babel-plugin-add-react-displayname": "0.0.5", 63 | "check-node-version": "^4.0.3", 64 | "enzyme": "^3.10.0", 65 | "enzyme-adapter-react-16": "^1.15.4", 66 | "eslint": "^6.8.0", 67 | "eslint-config-prettier": "^6.11.0", 68 | "eslint-config-react": "^1.1.7", 69 | "eslint-config-standard": "^14.1.0", 70 | "eslint-plugin-babel": "^5.3.1", 71 | "eslint-plugin-filenames": "^1.3.2", 72 | "eslint-plugin-import": "^2.22.0", 73 | "eslint-plugin-jasmine": "^2.10.1", 74 | "eslint-plugin-jest": "^22.17.0", 75 | "eslint-plugin-jsx-a11y": "^6.3.1", 76 | "eslint-plugin-node": "^9.2.0", 77 | "eslint-plugin-prettier": "^3.1.4", 78 | "eslint-plugin-promise": "^4.2.1", 79 | "eslint-plugin-react": "^7.20.6", 80 | "eslint-plugin-react-perf": "^3.2.4", 81 | "eslint-plugin-standard": "^4.0.1", 82 | "eslint-plugin-webdriverio": "^1.0.1", 83 | "husky": "^3.0.5", 84 | "jest": "^24.9.0", 85 | "npm-run-all": "^4.1.5", 86 | "prettier": "^1.18.2", 87 | "react": "16.6.3", 88 | "react-dom": "16.6.3", 89 | "stylelint": "^13.7.0", 90 | "stylelint-config-prettier": "^5.2.0", 91 | "stylelint-config-recommended": "^2.2.0", 92 | "stylelint-scss": "^3.18.0", 93 | "ts-jest": "^24.1.0", 94 | "tslint": "^6.1.3", 95 | "typescript": "^3.9.7" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/components/account-dropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dropdown, 3 | DropdownItem, 4 | NerdGraphQuery, 5 | Spinner, 6 | UserStorageMutation, 7 | UserStorageQuery, 8 | nerdlet, 9 | } from 'nr1'; 10 | import PropTypes from 'prop-types'; 11 | import React from 'react'; 12 | 13 | const documentId = 'default-account'; 14 | // Account query with extra data than AccountsQuery returns 15 | const ACCOUNT_QUERY = ` 16 | { 17 | actor { 18 | accounts { 19 | name 20 | id 21 | reportingEventTypes 22 | } 23 | } 24 | }`; 25 | 26 | export class AccountDropdown extends React.Component { 27 | static propTypes = { 28 | accountFilter: PropTypes.func, 29 | className: PropTypes.any, 30 | collection: PropTypes.string, 31 | onLoaded: PropTypes.func, 32 | onSelect: PropTypes.func, 33 | style: PropTypes.any, 34 | title: PropTypes.string, 35 | urlState: PropTypes.object, 36 | }; 37 | 38 | static defaultProps = { 39 | accountFilter: account => true, 40 | collection: 'newrelic', 41 | title: 'Select account...', 42 | }; 43 | 44 | constructor(props) { 45 | super(props); 46 | 47 | this.state = { 48 | accounts: null, 49 | defaultAccount: undefined, 50 | selected: null, 51 | }; 52 | 53 | this.select = this.select.bind(this); 54 | } 55 | 56 | async componentDidMount() { 57 | await Promise.all([this.loadAccounts(), this.loadDefaultAccount()]); 58 | this.setState(state => { 59 | if (this.props.urlState && this.props.urlState.account) { 60 | const account = this.state.accounts.find( 61 | account => account.id === this.props.urlState.account 62 | ); 63 | if (account) { 64 | return { 65 | selected: account, 66 | selectedFromUrlState: true, 67 | }; 68 | } 69 | } 70 | 71 | if (state.selected === null && state.defaultAccount && state.accounts) { 72 | const account = this.state.accounts.find( 73 | account => account.id === this.state.defaultAccount 74 | ); 75 | if (account) { 76 | return { 77 | selected: account, 78 | }; 79 | } 80 | } 81 | 82 | return null; 83 | }); 84 | } 85 | 86 | async componentDidUpdate(prevProps, prevState) { 87 | const prevAccount = prevState.selected; 88 | const account = this.state.selected; 89 | 90 | if (account && (!prevAccount || account.id !== prevAccount.id)) { 91 | this.props.onSelect(account); 92 | 93 | if (!this.state.selectedFromUrlState) { 94 | if (this.state.selected.id !== this.state.defaultAccount) { 95 | this.updateDefaultAccount(this.state.selected); 96 | } 97 | 98 | if (this.props.urlState && this.state.selected.id !== this.props.urlState.account) { 99 | nerdlet.setUrlState({ 100 | account: this.state.selected.id, 101 | }); 102 | } 103 | } 104 | } 105 | } 106 | 107 | async loadDefaultAccount() { 108 | const { collection } = this.props; 109 | 110 | const result = await UserStorageQuery.query({ collection, documentId }); 111 | const id = ((((result.data || {}).actor || {}).nerdStorage || {}).document || {}).id || null; 112 | this.setState(() => ({ 113 | defaultAccount: id, 114 | })); 115 | } 116 | 117 | async loadAccounts() { 118 | const result = await NerdGraphQuery.query({ query: ACCOUNT_QUERY }); 119 | const accounts = (((result || {}).data || {}).actor || {}).accounts || []; 120 | 121 | this.setState( 122 | { 123 | accounts, 124 | accountsById: accounts.reduce((result, account) => { 125 | result[account.id] = account; 126 | return result; 127 | }, {}), 128 | }, 129 | () => { 130 | this.props.onLoaded(this.state.accounts); 131 | } 132 | ); 133 | } 134 | 135 | async updateDefaultAccount(account) { 136 | const { collection } = this.props; 137 | 138 | await UserStorageMutation.mutate({ 139 | actionType: UserStorageMutation.ACTION_TYPE.WRITE_DOCUMENT, 140 | collection, 141 | document: { id: account.id }, 142 | documentId, 143 | }); 144 | 145 | this.setState({ 146 | defaultAccount: account.id, 147 | }); 148 | } 149 | 150 | select(account) { 151 | this.setState(state => { 152 | if (!state.selected || state.selected.id !== account.id) { 153 | return { 154 | selected: account, 155 | selectedFromUrlState: false, 156 | }; 157 | } 158 | 159 | return {}; 160 | }); 161 | } 162 | 163 | render() { 164 | const { accountFilter, className, style, title } = this.props; 165 | const { accounts, defaultAccount, selected } = this.state; 166 | let items; 167 | 168 | if (!accounts || defaultAccount === undefined) { 169 | return ; 170 | } 171 | 172 | const filteredAccounts = accounts.filter(accountFilter); 173 | 174 | if (accounts && filteredAccounts.length === 0) { 175 | items = No accounts found for this Nerdpack or for your user.; 176 | } else { 177 | items = filteredAccounts.map(account => ( 178 | this.select(account)}> 179 | {account.name} 180 | 181 | )); 182 | } 183 | 184 | return ( 185 | 186 | {items} 187 | 188 | ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/components/time-range/index.js: -------------------------------------------------------------------------------- 1 | export const durationToAbsoluteRange = timeRange => { 2 | if (!timeRange) { 3 | return durationToAbsoluteRange({ 4 | duration: 30 * 60 * 1000, 5 | }); 6 | } else if (timeRange.begin_time && timeRange.end_time) { 7 | return timeRange; 8 | } else if (timeRange.duration) { 9 | const now = Math.floor(new Date().getTime() / 1000); 10 | return { 11 | begin_time: now - timeRange.duration / 1000, 12 | end_time: now, 13 | }; 14 | } 15 | }; 16 | 17 | /* 18 | * Helper function to turn timeRange into NRQL Since 19 | */ 20 | export const timeRangeToNrql = timeRange => { 21 | if (!timeRange) { 22 | return ''; 23 | } else if (timeRange.begin_time && timeRange.end_time) { 24 | return ` SINCE ${timeRange.begin_time} UNTIL ${timeRange.end_time}`; 25 | } else if (timeRange.duration) { 26 | return ` SINCE ${timeRange.duration / 1000} SECONDS AGO`; 27 | } 28 | 29 | return ''; 30 | }; 31 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | // NerdGraph query wrapper for NRQL 2 | export const NERDGRAPH_NRQL_QUERY = ` 3 | query ($accountid:Int!, $nrql:Nrql!) { 4 | actor { 5 | account(id: $accountid) { 6 | nrql(query: $nrql, timeout: 10) { 7 | results 8 | } 9 | } 10 | } 11 | }`; 12 | -------------------------------------------------------------------------------- /src/lib/accounts-with-data.js: -------------------------------------------------------------------------------- 1 | import { NerdGraphQuery } from 'nr1'; 2 | 3 | /** 4 | * For building account pickers, etc. Get the list of all visible accounts that have 5 | * data for the given event type. For examlpe, if you send `SystemSample` you'll get 6 | * a list of all accounts with Infratructure installed. Clean up those account menus! 7 | */ 8 | export default async function accountsWithData(eventType) { 9 | const gql = `{actor {accounts {name id reportingEventTypes(filter:["${eventType}"])}}}`; 10 | const result = await NerdGraphQuery.query({ query: gql }); 11 | if (result.error) { 12 | console.warn( 13 | "Can't get reporting event types because NRDB is grumpy at NerdGraph.", 14 | result.error 15 | ); 16 | console.warn(JSON.stringify(result.error?.graphQLErrors.slice(0, 5), 0, 2)); 17 | return []; 18 | } 19 | 20 | return result.data.actor.accounts.filter(a => a.reportingEventTypes.length > 0); 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/bytes-to-size.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Convert bytes to human readable bytes 3 | */ 4 | export const bytesToSize = (bytes, hideUnit) => { 5 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; 6 | if (bytes === 0) return `0 ${!hideUnit ? 'B' : ''}`; 7 | const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10); 8 | if (i === 0) return `${bytes.toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; 9 | return `${(bytes / 1024 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; 10 | }; 11 | 12 | export const bitsToSize = (bits, hideUnit) => { 13 | const sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb']; 14 | if (bits === 0) return `0 ${!hideUnit ? 'b' : ''}`; 15 | const i = parseInt(Math.floor(Math.log(bits) / Math.log(1024)), 10); 16 | if (i === 0) return `${bits.toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; 17 | return `${(bits / 1024 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; 18 | }; 19 | 20 | export const intToSize = (num, hideUnit) => { 21 | const sizes = ['', 'K', 'M', 'B', 'T']; 22 | if (num === 0) return '0'; 23 | const i = parseInt(Math.floor(Math.log(num) / Math.log(1000)), 10); 24 | if (i === 0) return `${num.toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; 25 | return `${(num / 1000 ** i).toFixed(1)} ${!hideUnit ? sizes[i] : ''}`; 26 | }; 27 | -------------------------------------------------------------------------------- /src/lib/find-related-account-with.js: -------------------------------------------------------------------------------- 1 | import accountsWithData from './accounts-with-data'; 2 | import nrdbQuery from './nrdb-query'; 3 | 4 | /** 5 | * look across all the accounts the user has access to, scoped to the provided 6 | * event type, and find any accounts that have a match on the provided where clause. 7 | * Useful for connecting entities/guids etc across account boundaries. 8 | * 9 | * As an optimization, we only query accounts that have event data of the provided type. 10 | * Beware that for customers with lots of accounts and a common event type (e.g. Transaction) 11 | * this could take a while. By default we use a short time window to keep queries light. 12 | * 13 | * Run account queries in parallel (limited by the browser's capacity for parallel requests) 14 | * and return the array of accounts, each with a hit count in descending order. 15 | */ 16 | export default async function findRelatedAccountsWith({ eventType, where, timeWindow }) { 17 | timeWindow = timeWindow || 'SINCE 2 minutes ago'; 18 | 19 | const accounts = await accountsWithData(eventType); 20 | const nrql = `SELECT count(*) FROM ${eventType} WHERE ${where} ${timeWindow}`; 21 | 22 | const result = []; 23 | await Promise.all( 24 | accounts.map(async account => { 25 | return nrdbQuery(account.id, nrql).then(results => { 26 | const hitCount = results[0].count; 27 | if (hitCount > 0) { 28 | account.hitCount = hitCount; 29 | result.push(account); 30 | } 31 | }); 32 | }) 33 | ); 34 | 35 | return result.sort((a, b) => b.hitCount - a.hitCount); 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/nrdb-query.js: -------------------------------------------------------------------------------- 1 | import { NerdGraphQuery } from 'nr1'; 2 | 3 | export default async function nrdbQuery(accountId, nrql) { 4 | if (!nrql) { 5 | console.warn('You probably forgot to provide an accountId', accountId); 6 | throw nrql; 7 | } 8 | 9 | nrql = nrql.replace(/\n/g, ' '); 10 | const gql = `{ 11 | actor { 12 | account(id: ${accountId}) { 13 | nrql(query: "${nrql}") { 14 | results 15 | } 16 | } 17 | } 18 | }`; 19 | 20 | try { 21 | const { data, error } = await NerdGraphQuery.query({ query: gql }); 22 | if (error || !data.actor.account.nrql) { 23 | throw new Error('Bad NRQL Query: ' + nrql + ': ' + error); 24 | } 25 | 26 | return data.actor.account.nrql.results; 27 | } catch (e) { 28 | console.warn('NRDB Query Error', nrql, e); 29 | throw e; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/nrql.js: -------------------------------------------------------------------------------- 1 | import { NerdGraphQuery } from 'nr1'; 2 | 3 | // NerdGraph query wrapper for NRQL 4 | export const NERDGRAPH_NRQL_QUERY = ` 5 | query ($accountid:Int!, $nrql:Nrql!) { 6 | actor { 7 | account(id: $accountid) { 8 | nrql(query: $nrql, timeout: 10) { 9 | results 10 | } 11 | } 12 | } 13 | }`; 14 | 15 | /* 16 | * Make an NRQL query through NerdGraph, return just the result json 17 | */ 18 | export const fetchNrqlResults = async (accountId, query) => { 19 | if (!accountId || !query) return; 20 | 21 | const results = await NerdGraphQuery.query({ 22 | query: NERDGRAPH_NRQL_QUERY, 23 | variables: { 24 | accountid: accountId, 25 | nrql: query, 26 | }, 27 | }); 28 | 29 | return ((((results.data || {}).actor || {}).account || {}).nrql || {}).results || []; 30 | }; 31 | -------------------------------------------------------------------------------- /test/test.test.js: -------------------------------------------------------------------------------- 1 | test('first test!', () => { 2 | expect(true).toBe(true); 3 | }); 4 | -------------------------------------------------------------------------------- /test/utils/mocks/styleMock.jsx: -------------------------------------------------------------------------------- 1 | // Used to mock out style file imports for Jest, so it ignores them. 2 | const mock = {}; 3 | 4 | export default mock; 5 | -------------------------------------------------------------------------------- /test/utils/setupTests.jsx: -------------------------------------------------------------------------------- 1 | import Adapter from 'enzyme-adapter-react-16'; 2 | import { configure } from 'enzyme'; 3 | 4 | // Sets up enzyme to know what version of react its working with. 5 | configure({ adapter: new Adapter() }); 6 | -------------------------------------------------------------------------------- /third_party_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "lastUpdated": "Thu Sep 03 2020 04:08:13 GMT+0000 (Coordinated Universal Time)", 3 | "projectName": "nr1-network-telemetry", 4 | "projectUrl": "https://github.com/newrelic/nr1-network-telemetry", 5 | "includeDev": true, 6 | "dependencies": { 7 | "@newrelic/nr1-community@1.2.0": { 8 | "name": "@newrelic/nr1-community", 9 | "version": "1.2.0", 10 | "range": "^1.2.0", 11 | "licenses": "Apache-2.0", 12 | "repoUrl": "https://github.com/newrelic/nr1-community", 13 | "versionedRepoUrl": "https://github.com/newrelic/nr1-community/tree/v1.2.0", 14 | "licenseFile": "node_modules/@newrelic/nr1-community/LICENSE", 15 | "licenseUrl": "https://github.com/newrelic/nr1-community/blob/v1.2.0/LICENSE", 16 | "licenseTextSource": "file", 17 | "publisher": "newrelic" 18 | }, 19 | "d3@3.5.17": { 20 | "name": "d3", 21 | "version": "3.5.17", 22 | "range": "3.5.17", 23 | "licenses": "BSD-3-Clause", 24 | "repoUrl": "https://github.com/mbostock/d3", 25 | "versionedRepoUrl": "https://github.com/mbostock/d3/tree/v3.5.17", 26 | "licenseFile": "node_modules/d3/LICENSE", 27 | "licenseUrl": "https://github.com/mbostock/d3/blob/v3.5.17/LICENSE", 28 | "licenseTextSource": "file", 29 | "publisher": "Mike Bostock", 30 | "url": "http://bost.ocks.org/mike" 31 | }, 32 | "lodash@4.17.20": { 33 | "name": "lodash", 34 | "version": "4.17.20", 35 | "range": "^4.17.20", 36 | "licenses": "MIT", 37 | "repoUrl": "https://github.com/lodash/lodash", 38 | "versionedRepoUrl": "https://github.com/lodash/lodash/tree/v4.17.20", 39 | "licenseFile": "node_modules/lodash/LICENSE", 40 | "licenseUrl": "https://github.com/lodash/lodash/blob/v4.17.20/LICENSE", 41 | "licenseTextSource": "file", 42 | "publisher": "John-David Dalton", 43 | "email": "john.david.dalton@gmail.com" 44 | }, 45 | "prop-types@15.7.2": { 46 | "name": "prop-types", 47 | "version": "15.7.2", 48 | "range": "^15.6.2", 49 | "licenses": "MIT", 50 | "repoUrl": "https://github.com/facebook/prop-types", 51 | "versionedRepoUrl": "https://github.com/facebook/prop-types/tree/v15.7.2", 52 | "licenseFile": "node_modules/prop-types/LICENSE", 53 | "licenseUrl": "https://github.com/facebook/prop-types/blob/v15.7.2/LICENSE", 54 | "licenseTextSource": "file" 55 | }, 56 | "react-chord-diagram@1.6.0": { 57 | "name": "react-chord-diagram", 58 | "version": "1.6.0", 59 | "range": "^1.5.0", 60 | "licenses": "MIT", 61 | "repoUrl": "https://github.com/graysoncl/react-chord-diagram", 62 | "versionedRepoUrl": "https://github.com/graysoncl/react-chord-diagram/tree/v1.6.0", 63 | "licenseFile": "node_modules/react-chord-diagram/LICENSE", 64 | "licenseUrl": "https://github.com/graysoncl/react-chord-diagram/blob/v1.6.0/LICENSE", 65 | "licenseTextSource": "file", 66 | "publisher": "Grayson Langford" 67 | }, 68 | "react-input-range@1.3.0": { 69 | "name": "react-input-range", 70 | "version": "1.3.0", 71 | "range": "^1.3.0", 72 | "licenses": "MIT", 73 | "repoUrl": "https://github.com/davidchin/react-input-range", 74 | "versionedRepoUrl": "https://github.com/davidchin/react-input-range/tree/v1.3.0", 75 | "licenseFile": "node_modules/react-input-range/LICENSE", 76 | "licenseUrl": "https://github.com/davidchin/react-input-range/blob/v1.3.0/LICENSE", 77 | "licenseTextSource": "file", 78 | "publisher": "David Chin" 79 | }, 80 | "react-vis@1.11.7": { 81 | "name": "react-vis", 82 | "version": "1.11.7", 83 | "range": "^1.11.7", 84 | "licenses": "MIT", 85 | "repoUrl": "https://github.com/uber-common/react-vis", 86 | "versionedRepoUrl": "https://github.com/uber-common/react-vis/tree/v1.11.7", 87 | "licenseFile": "node_modules/react-vis/LICENSE", 88 | "licenseUrl": "https://github.com/uber-common/react-vis/blob/v1.11.7/LICENSE", 89 | "licenseTextSource": "file", 90 | "publisher": "Visualization Team", 91 | "email": "visualization@uber.com" 92 | }, 93 | "semantic-ui-react@0.88.2": { 94 | "name": "semantic-ui-react", 95 | "version": "0.88.2", 96 | "range": "^0.88.1", 97 | "licenses": "MIT", 98 | "repoUrl": "https://github.com/Semantic-Org/Semantic-UI-React", 99 | "versionedRepoUrl": "https://github.com/Semantic-Org/Semantic-UI-React/tree/v0.88.2", 100 | "licenseFile": "node_modules/semantic-ui-react/LICENSE.md", 101 | "licenseUrl": "https://github.com/Semantic-Org/Semantic-UI-React/blob/v0.88.2/LICENSE.md", 102 | "licenseTextSource": "file", 103 | "publisher": "Levi Thomason", 104 | "email": "me@levithomason.com" 105 | } 106 | }, 107 | "devDependencies": { 108 | "@babel/plugin-proposal-class-properties@7.10.4": { 109 | "name": "@babel/plugin-proposal-class-properties", 110 | "version": "7.10.4", 111 | "range": "^7.10.4", 112 | "licenses": "MIT", 113 | "repoUrl": "https://github.com/babel/babel", 114 | "versionedRepoUrl": "https://github.com/babel/babel/tree/v7.10.4", 115 | "licenseFile": "node_modules/@babel/plugin-proposal-class-properties/LICENSE", 116 | "licenseUrl": "https://github.com/babel/babel/blob/v7.10.4/LICENSE", 117 | "licenseTextSource": "file" 118 | }, 119 | "@babel/plugin-proposal-object-rest-spread@7.11.0": { 120 | "name": "@babel/plugin-proposal-object-rest-spread", 121 | "version": "7.11.0", 122 | "range": "^7.11.0", 123 | "licenses": "MIT", 124 | "repoUrl": "https://github.com/babel/babel", 125 | "versionedRepoUrl": "https://github.com/babel/babel/tree/v7.11.0", 126 | "licenseFile": "node_modules/@babel/plugin-proposal-object-rest-spread/LICENSE", 127 | "licenseUrl": "https://github.com/babel/babel/blob/v7.11.0/LICENSE", 128 | "licenseTextSource": "file" 129 | }, 130 | "@babel/plugin-transform-runtime@7.11.5": { 131 | "name": "@babel/plugin-transform-runtime", 132 | "version": "7.11.5", 133 | "range": "^7.11.5", 134 | "licenses": "MIT", 135 | "repoUrl": "https://github.com/babel/babel", 136 | "versionedRepoUrl": "https://github.com/babel/babel/tree/v7.11.5", 137 | "licenseFile": "node_modules/@babel/plugin-transform-runtime/LICENSE", 138 | "licenseUrl": "https://github.com/babel/babel/blob/v7.11.5/LICENSE", 139 | "licenseTextSource": "file" 140 | }, 141 | "@babel/preset-env@7.11.5": { 142 | "name": "@babel/preset-env", 143 | "version": "7.11.5", 144 | "range": "^7.11.5", 145 | "licenses": "MIT", 146 | "repoUrl": "https://github.com/babel/babel", 147 | "versionedRepoUrl": "https://github.com/babel/babel/tree/v7.11.5", 148 | "licenseFile": "node_modules/@babel/preset-env/LICENSE", 149 | "licenseUrl": "https://github.com/babel/babel/blob/v7.11.5/LICENSE", 150 | "licenseTextSource": "file", 151 | "publisher": "Henry Zhu", 152 | "email": "hi@henryzoo.com" 153 | }, 154 | "@babel/preset-react@7.10.4": { 155 | "name": "@babel/preset-react", 156 | "version": "7.10.4", 157 | "range": "^7.10.4", 158 | "licenses": "MIT", 159 | "repoUrl": "https://github.com/babel/babel", 160 | "versionedRepoUrl": "https://github.com/babel/babel/tree/v7.10.4", 161 | "licenseFile": "node_modules/@babel/preset-react/LICENSE", 162 | "licenseUrl": "https://github.com/babel/babel/blob/v7.10.4/LICENSE", 163 | "licenseTextSource": "file", 164 | "publisher": "Sebastian McKenzie", 165 | "email": "sebmck@gmail.com" 166 | }, 167 | "@commitlint/cli@8.3.5": { 168 | "name": "@commitlint/cli", 169 | "version": "8.3.5", 170 | "range": "^8.3.5", 171 | "licenses": "MIT", 172 | "repoUrl": "https://github.com/conventional-changelog/commitlint", 173 | "versionedRepoUrl": "https://github.com/conventional-changelog/commitlint/tree/v8.3.5", 174 | "licenseFile": "node_modules/@commitlint/cli/license.md", 175 | "licenseUrl": "https://github.com/conventional-changelog/commitlint/blob/v8.3.5/license.md", 176 | "licenseTextSource": "file", 177 | "publisher": "Mario Nebl", 178 | "email": "hello@herebecode.com" 179 | }, 180 | "@commitlint/config-conventional@8.3.4": { 181 | "name": "@commitlint/config-conventional", 182 | "version": "8.3.4", 183 | "range": "^8.1.0", 184 | "licenses": "MIT", 185 | "repoUrl": "https://github.com/conventional-changelog/commitlint", 186 | "versionedRepoUrl": "https://github.com/conventional-changelog/commitlint/tree/v8.3.4", 187 | "licenseFile": "node_modules/@commitlint/config-conventional/license.md", 188 | "licenseUrl": "https://github.com/conventional-changelog/commitlint/blob/v8.3.4/license.md", 189 | "licenseTextSource": "file", 190 | "publisher": "Mario Nebl", 191 | "email": "hello@herebecode.com" 192 | }, 193 | "@newrelic/eslint-plugin-newrelic@0.3.1": { 194 | "name": "@newrelic/eslint-plugin-newrelic", 195 | "version": "0.3.1", 196 | "range": "^0.3.1", 197 | "licenses": "Apache-2.0", 198 | "repoUrl": "https://github.com/NewRelic/eslint-plugin-newrelic", 199 | "versionedRepoUrl": "https://github.com/NewRelic/eslint-plugin-newrelic/tree/v0.3.1", 200 | "licenseFile": "node_modules/@newrelic/eslint-plugin-newrelic/LICENSE", 201 | "licenseUrl": "https://github.com/NewRelic/eslint-plugin-newrelic/blob/v0.3.1/LICENSE", 202 | "licenseTextSource": "file", 203 | "publisher": "New Relic" 204 | }, 205 | "@semantic-release/changelog@5.0.1": { 206 | "name": "@semantic-release/changelog", 207 | "version": "5.0.1", 208 | "range": "^5.0.1", 209 | "licenses": "MIT", 210 | "repoUrl": "https://github.com/semantic-release/changelog", 211 | "versionedRepoUrl": "https://github.com/semantic-release/changelog/tree/v5.0.1", 212 | "licenseFile": "node_modules/@semantic-release/changelog/LICENSE", 213 | "licenseUrl": "https://github.com/semantic-release/changelog/blob/v5.0.1/LICENSE", 214 | "licenseTextSource": "file", 215 | "publisher": "Pierre Vanduynslager", 216 | "url": "https://github.com/pvdlg" 217 | }, 218 | "@semantic-release/git@9.0.0": { 219 | "name": "@semantic-release/git", 220 | "version": "9.0.0", 221 | "range": "^9.0.0", 222 | "licenses": "MIT", 223 | "repoUrl": "https://github.com/semantic-release/git", 224 | "versionedRepoUrl": "https://github.com/semantic-release/git/tree/v9.0.0", 225 | "licenseFile": "node_modules/@semantic-release/git/LICENSE", 226 | "licenseUrl": "https://github.com/semantic-release/git/blob/v9.0.0/LICENSE", 227 | "licenseTextSource": "file", 228 | "publisher": "Pierre Vanduynslager", 229 | "url": "https://github.com/pvdlg" 230 | }, 231 | "@typescript-eslint/eslint-plugin@2.34.0": { 232 | "name": "@typescript-eslint/eslint-plugin", 233 | "version": "2.34.0", 234 | "range": "^2.34.0", 235 | "licenses": "MIT", 236 | "repoUrl": "https://github.com/typescript-eslint/typescript-eslint", 237 | "versionedRepoUrl": "https://github.com/typescript-eslint/typescript-eslint/tree/v2.34.0", 238 | "licenseFile": "node_modules/@typescript-eslint/eslint-plugin/LICENSE", 239 | "licenseUrl": "https://github.com/typescript-eslint/typescript-eslint/blob/v2.34.0/LICENSE", 240 | "licenseTextSource": "file" 241 | }, 242 | "@typescript-eslint/experimental-utils@2.34.0": { 243 | "name": "@typescript-eslint/experimental-utils", 244 | "version": "2.34.0", 245 | "range": "^2.34.0", 246 | "licenses": "MIT", 247 | "repoUrl": "https://github.com/typescript-eslint/typescript-eslint", 248 | "versionedRepoUrl": "https://github.com/typescript-eslint/typescript-eslint/tree/v2.34.0", 249 | "licenseFile": "node_modules/@typescript-eslint/experimental-utils/LICENSE", 250 | "licenseUrl": "https://github.com/typescript-eslint/typescript-eslint/blob/v2.34.0/LICENSE", 251 | "licenseTextSource": "file" 252 | }, 253 | "@typescript-eslint/parser@2.34.0": { 254 | "name": "@typescript-eslint/parser", 255 | "version": "2.34.0", 256 | "range": "^2.34.0", 257 | "licenses": "BSD-2-Clause", 258 | "repoUrl": "https://github.com/typescript-eslint/typescript-eslint", 259 | "versionedRepoUrl": "https://github.com/typescript-eslint/typescript-eslint/tree/v2.34.0", 260 | "licenseFile": "node_modules/@typescript-eslint/parser/LICENSE", 261 | "licenseUrl": "https://github.com/typescript-eslint/typescript-eslint/blob/v2.34.0/LICENSE", 262 | "licenseTextSource": "file" 263 | }, 264 | "babel-core@6.26.3": { 265 | "name": "babel-core", 266 | "version": "6.26.3", 267 | "range": "^6.26.3", 268 | "licenses": "MIT", 269 | "repoUrl": "https://github.com/babel/babel/tree/master/packages/babel-core", 270 | "versionedRepoUrl": "https://github.com/babel/babel/tree/master/packages/babel-core/tree/v6.26.3", 271 | "licenseFile": "node_modules/babel-core/README.md", 272 | "licenseUrl": "https://github.com/babel/babel/tree/master/packages/babel-core/blob/v6.26.3/README.md", 273 | "licenseTextSource": "spdx", 274 | "publisher": "Sebastian McKenzie", 275 | "email": "sebmck@gmail.com" 276 | }, 277 | "babel-eslint@10.1.0": { 278 | "name": "babel-eslint", 279 | "version": "10.1.0", 280 | "range": "^10.0.3", 281 | "licenses": "MIT", 282 | "repoUrl": "https://github.com/babel/babel-eslint", 283 | "versionedRepoUrl": "https://github.com/babel/babel-eslint/tree/v10.1.0", 284 | "licenseFile": "node_modules/babel-eslint/LICENSE", 285 | "licenseUrl": "https://github.com/babel/babel-eslint/blob/v10.1.0/LICENSE", 286 | "licenseTextSource": "file", 287 | "publisher": "Sebastian McKenzie", 288 | "email": "sebmck@gmail.com" 289 | }, 290 | "babel-jest@24.9.0": { 291 | "name": "babel-jest", 292 | "version": "24.9.0", 293 | "range": "^24.9.0", 294 | "licenses": "MIT", 295 | "repoUrl": "https://github.com/facebook/jest", 296 | "versionedRepoUrl": "https://github.com/facebook/jest/tree/v24.9.0", 297 | "licenseFile": "node_modules/babel-jest/README.md", 298 | "licenseUrl": "https://github.com/facebook/jest/blob/v24.9.0/README.md", 299 | "licenseTextSource": "spdx" 300 | }, 301 | "babel-plugin-add-react-displayname@0.0.5": { 302 | "name": "babel-plugin-add-react-displayname", 303 | "version": "0.0.5", 304 | "range": "0.0.5", 305 | "licenses": "MIT", 306 | "repoUrl": "https://github.com/opbeat/babel-plugin-add-react-displayname", 307 | "versionedRepoUrl": "https://github.com/opbeat/babel-plugin-add-react-displayname/tree/v0.0.5", 308 | "licenseFile": "node_modules/babel-plugin-add-react-displayname/README.md", 309 | "licenseUrl": "https://github.com/opbeat/babel-plugin-add-react-displayname/blob/v0.0.5/README.md", 310 | "licenseTextSource": "spdx", 311 | "publisher": "Ron Cohen" 312 | }, 313 | "check-node-version@4.0.3": { 314 | "name": "check-node-version", 315 | "version": "4.0.3", 316 | "range": "^4.0.3", 317 | "licenses": "Unlicense", 318 | "repoUrl": "https://github.com/parshap/check-node-version", 319 | "versionedRepoUrl": "https://github.com/parshap/check-node-version/tree/v4.0.3", 320 | "licenseFile": "node_modules/check-node-version/LICENSE", 321 | "licenseUrl": "https://github.com/parshap/check-node-version/blob/v4.0.3/LICENSE", 322 | "licenseTextSource": "file", 323 | "publisher": "Parsha Pourkhomami" 324 | }, 325 | "enzyme-adapter-react-16@1.15.4": { 326 | "name": "enzyme-adapter-react-16", 327 | "version": "1.15.4", 328 | "range": "^1.15.4", 329 | "licenses": "MIT", 330 | "repoUrl": "https://github.com/enzymejs/enzyme", 331 | "versionedRepoUrl": "https://github.com/enzymejs/enzyme/tree/v1.15.4", 332 | "licenseFile": "node_modules/enzyme-adapter-react-16/LICENSE.md", 333 | "licenseUrl": "https://github.com/enzymejs/enzyme/blob/v1.15.4/LICENSE.md", 334 | "licenseTextSource": "file", 335 | "publisher": "Jordan Harband", 336 | "email": "ljharb@gmail.com" 337 | }, 338 | "enzyme@3.11.0": { 339 | "name": "enzyme", 340 | "version": "3.11.0", 341 | "range": "^3.10.0", 342 | "licenses": "MIT", 343 | "repoUrl": "https://github.com/airbnb/enzyme", 344 | "versionedRepoUrl": "https://github.com/airbnb/enzyme/tree/v3.11.0", 345 | "licenseFile": "node_modules/enzyme/LICENSE.md", 346 | "licenseUrl": "https://github.com/airbnb/enzyme/blob/v3.11.0/LICENSE.md", 347 | "licenseTextSource": "file", 348 | "publisher": "Leland Richardson", 349 | "email": "leland.richardson@airbnb.com" 350 | }, 351 | "eslint-config-prettier@6.11.0": { 352 | "name": "eslint-config-prettier", 353 | "version": "6.11.0", 354 | "range": "^6.11.0", 355 | "licenses": "MIT", 356 | "repoUrl": "https://github.com/prettier/eslint-config-prettier", 357 | "versionedRepoUrl": "https://github.com/prettier/eslint-config-prettier/tree/v6.11.0", 358 | "licenseFile": "node_modules/eslint-config-prettier/LICENSE", 359 | "licenseUrl": "https://github.com/prettier/eslint-config-prettier/blob/v6.11.0/LICENSE", 360 | "licenseTextSource": "file", 361 | "publisher": "Simon Lydell" 362 | }, 363 | "eslint-config-react@1.1.7": { 364 | "name": "eslint-config-react", 365 | "version": "1.1.7", 366 | "range": "^1.1.7", 367 | "licenses": "MIT", 368 | "repoUrl": "https://github.com/patrio/eslint-config-react", 369 | "versionedRepoUrl": "https://github.com/patrio/eslint-config-react/tree/v1.1.7", 370 | "licenseFile": "node_modules/eslint-config-react/LICENSE", 371 | "licenseUrl": "https://github.com/patrio/eslint-config-react/blob/v1.1.7/LICENSE", 372 | "licenseTextSource": "file", 373 | "publisher": "Patric Nordmark", 374 | "email": "patric.nordmark@dice.se" 375 | }, 376 | "eslint-config-standard@14.1.1": { 377 | "name": "eslint-config-standard", 378 | "version": "14.1.1", 379 | "range": "^14.1.0", 380 | "licenses": "MIT", 381 | "repoUrl": "https://github.com/standard/eslint-config-standard", 382 | "versionedRepoUrl": "https://github.com/standard/eslint-config-standard/tree/v14.1.1", 383 | "licenseFile": "node_modules/eslint-config-standard/LICENSE", 384 | "licenseUrl": "https://github.com/standard/eslint-config-standard/blob/v14.1.1/LICENSE", 385 | "licenseTextSource": "file", 386 | "publisher": "Feross Aboukhadijeh", 387 | "email": "feross@feross.org", 388 | "url": "https://feross.org" 389 | }, 390 | "eslint-plugin-babel@5.3.1": { 391 | "name": "eslint-plugin-babel", 392 | "version": "5.3.1", 393 | "range": "^5.3.1", 394 | "licenses": "MIT", 395 | "repoUrl": "https://github.com/babel/eslint-plugin-babel", 396 | "versionedRepoUrl": "https://github.com/babel/eslint-plugin-babel/tree/v5.3.1", 397 | "licenseFile": "node_modules/eslint-plugin-babel/LICENSE", 398 | "licenseUrl": "https://github.com/babel/eslint-plugin-babel/blob/v5.3.1/LICENSE", 399 | "licenseTextSource": "file", 400 | "publisher": "Jason Quense @monasticpanic" 401 | }, 402 | "eslint-plugin-filenames@1.3.2": { 403 | "name": "eslint-plugin-filenames", 404 | "version": "1.3.2", 405 | "range": "^1.3.2", 406 | "licenses": "MIT", 407 | "repoUrl": "https://github.com/selaux/eslint-plugin-filenames", 408 | "versionedRepoUrl": "https://github.com/selaux/eslint-plugin-filenames/tree/v1.3.2", 409 | "licenseFile": "node_modules/eslint-plugin-filenames/README.md", 410 | "licenseUrl": "https://github.com/selaux/eslint-plugin-filenames/blob/v1.3.2/README.md", 411 | "licenseTextSource": "spdx", 412 | "publisher": "Stefan Lau", 413 | "email": "github@stefanlau.com" 414 | }, 415 | "eslint-plugin-import@2.22.0": { 416 | "name": "eslint-plugin-import", 417 | "version": "2.22.0", 418 | "range": "^2.22.0", 419 | "licenses": "MIT", 420 | "repoUrl": "https://github.com/benmosher/eslint-plugin-import", 421 | "versionedRepoUrl": "https://github.com/benmosher/eslint-plugin-import/tree/v2.22.0", 422 | "licenseFile": "node_modules/eslint-plugin-import/LICENSE", 423 | "licenseUrl": "https://github.com/benmosher/eslint-plugin-import/blob/v2.22.0/LICENSE", 424 | "licenseTextSource": "file", 425 | "publisher": "Ben Mosher", 426 | "email": "me@benmosher.com" 427 | }, 428 | "eslint-plugin-jasmine@2.10.1": { 429 | "name": "eslint-plugin-jasmine", 430 | "version": "2.10.1", 431 | "range": "^2.10.1", 432 | "licenses": "MIT", 433 | "repoUrl": "https://github.com/tlvince/eslint-plugin-jasmine", 434 | "versionedRepoUrl": "https://github.com/tlvince/eslint-plugin-jasmine/tree/v2.10.1", 435 | "licenseFile": "node_modules/eslint-plugin-jasmine/README.md", 436 | "licenseUrl": "https://github.com/tlvince/eslint-plugin-jasmine/blob/v2.10.1/README.md", 437 | "licenseTextSource": "spdx", 438 | "publisher": "Tom Vincent", 439 | "email": "npm@tlvince.com", 440 | "url": "http://tlvince.com/" 441 | }, 442 | "eslint-plugin-jest@22.21.0": { 443 | "name": "eslint-plugin-jest", 444 | "version": "22.21.0", 445 | "range": "^22.17.0", 446 | "licenses": "MIT", 447 | "repoUrl": "https://github.com/jest-community/eslint-plugin-jest", 448 | "versionedRepoUrl": "https://github.com/jest-community/eslint-plugin-jest/tree/v22.21.0", 449 | "licenseFile": "node_modules/eslint-plugin-jest/LICENSE", 450 | "licenseUrl": "https://github.com/jest-community/eslint-plugin-jest/blob/v22.21.0/LICENSE", 451 | "licenseTextSource": "file", 452 | "publisher": "Jonathan Kim", 453 | "email": "hello@jkimbo.com", 454 | "url": "jkimbo.com" 455 | }, 456 | "eslint-plugin-jsx-a11y@6.3.1": { 457 | "name": "eslint-plugin-jsx-a11y", 458 | "version": "6.3.1", 459 | "range": "^6.3.1", 460 | "licenses": "MIT", 461 | "repoUrl": "https://github.com/evcohen/eslint-plugin-jsx-a11y", 462 | "versionedRepoUrl": "https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/v6.3.1", 463 | "licenseFile": "node_modules/eslint-plugin-jsx-a11y/LICENSE.md", 464 | "licenseUrl": "https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/v6.3.1/LICENSE.md", 465 | "licenseTextSource": "file", 466 | "publisher": "Ethan Cohen" 467 | }, 468 | "eslint-plugin-node@9.2.0": { 469 | "name": "eslint-plugin-node", 470 | "version": "9.2.0", 471 | "range": "^9.2.0", 472 | "licenses": "MIT", 473 | "repoUrl": "https://github.com/mysticatea/eslint-plugin-node", 474 | "versionedRepoUrl": "https://github.com/mysticatea/eslint-plugin-node/tree/v9.2.0", 475 | "licenseFile": "node_modules/eslint-plugin-node/LICENSE", 476 | "licenseUrl": "https://github.com/mysticatea/eslint-plugin-node/blob/v9.2.0/LICENSE", 477 | "licenseTextSource": "file", 478 | "publisher": "Toru Nagashima" 479 | }, 480 | "eslint-plugin-prettier@3.1.4": { 481 | "name": "eslint-plugin-prettier", 482 | "version": "3.1.4", 483 | "range": "^3.1.4", 484 | "licenses": "MIT", 485 | "repoUrl": "https://github.com/prettier/eslint-plugin-prettier", 486 | "versionedRepoUrl": "https://github.com/prettier/eslint-plugin-prettier/tree/v3.1.4", 487 | "licenseFile": "node_modules/eslint-plugin-prettier/LICENSE.md", 488 | "licenseUrl": "https://github.com/prettier/eslint-plugin-prettier/blob/v3.1.4/LICENSE.md", 489 | "licenseTextSource": "file", 490 | "publisher": "Teddy Katz" 491 | }, 492 | "eslint-plugin-promise@4.2.1": { 493 | "name": "eslint-plugin-promise", 494 | "version": "4.2.1", 495 | "range": "^4.2.1", 496 | "licenses": "ISC", 497 | "repoUrl": "https://github.com/xjamundx/eslint-plugin-promise", 498 | "versionedRepoUrl": "https://github.com/xjamundx/eslint-plugin-promise/tree/v4.2.1", 499 | "licenseFile": "node_modules/eslint-plugin-promise/README.md", 500 | "licenseUrl": "https://github.com/xjamundx/eslint-plugin-promise/blob/v4.2.1/README.md", 501 | "licenseTextSource": "spdx", 502 | "publisher": "jden", 503 | "email": "jason@denizac.org" 504 | }, 505 | "eslint-plugin-react-perf@3.2.4": { 506 | "name": "eslint-plugin-react-perf", 507 | "version": "3.2.4", 508 | "range": "^3.2.4", 509 | "licenses": "MIT", 510 | "repoUrl": "https://github.com/cvazac/eslint-plugin-react-perf", 511 | "versionedRepoUrl": "https://github.com/cvazac/eslint-plugin-react-perf/tree/v3.2.4", 512 | "licenseFile": "node_modules/eslint-plugin-react-perf/LICENSE", 513 | "licenseUrl": "https://github.com/cvazac/eslint-plugin-react-perf/blob/v3.2.4/LICENSE", 514 | "licenseTextSource": "file", 515 | "publisher": "Charles Vazac", 516 | "email": "cvazac@gmail.com" 517 | }, 518 | "eslint-plugin-react@7.20.6": { 519 | "name": "eslint-plugin-react", 520 | "version": "7.20.6", 521 | "range": "^7.20.6", 522 | "licenses": "MIT", 523 | "repoUrl": "https://github.com/yannickcr/eslint-plugin-react", 524 | "versionedRepoUrl": "https://github.com/yannickcr/eslint-plugin-react/tree/v7.20.6", 525 | "licenseFile": "node_modules/eslint-plugin-react/LICENSE", 526 | "licenseUrl": "https://github.com/yannickcr/eslint-plugin-react/blob/v7.20.6/LICENSE", 527 | "licenseTextSource": "file", 528 | "publisher": "Yannick Croissant", 529 | "email": "yannick.croissant+npm@gmail.com" 530 | }, 531 | "eslint-plugin-standard@4.0.1": { 532 | "name": "eslint-plugin-standard", 533 | "version": "4.0.1", 534 | "range": "^4.0.1", 535 | "licenses": "MIT", 536 | "repoUrl": "https://github.com/standard/eslint-plugin-standard", 537 | "versionedRepoUrl": "https://github.com/standard/eslint-plugin-standard/tree/v4.0.1", 538 | "licenseFile": "node_modules/eslint-plugin-standard/LICENSE", 539 | "licenseUrl": "https://github.com/standard/eslint-plugin-standard/blob/v4.0.1/LICENSE", 540 | "licenseTextSource": "file", 541 | "publisher": "Jamund Ferguson", 542 | "email": "jamund@gmail.com" 543 | }, 544 | "eslint-plugin-webdriverio@1.0.1": { 545 | "name": "eslint-plugin-webdriverio", 546 | "version": "1.0.1", 547 | "range": "^1.0.1", 548 | "licenses": "ISC", 549 | "repoUrl": "git+https://gitlab.com/sebdeckers/eslint-plugin-webdriverio", 550 | "versionedRepoUrl": "git+https://gitlab.com/sebdeckers/eslint-plugin-webdriverio/tree/v1.0.1", 551 | "licenseFile": "node_modules/eslint-plugin-webdriverio/README.md", 552 | "licenseUrl": "git+https://gitlab.com/sebdeckers/eslint-plugin-webdriverio/blob/v1.0.1/README.md", 553 | "licenseTextSource": "spdx", 554 | "publisher": "Sebastiaan Deckers", 555 | "email": "sebdeckers83@gmail.com" 556 | }, 557 | "eslint@6.8.0": { 558 | "name": "eslint", 559 | "version": "6.8.0", 560 | "range": "^6.8.0", 561 | "licenses": "MIT", 562 | "repoUrl": "https://github.com/eslint/eslint", 563 | "versionedRepoUrl": "https://github.com/eslint/eslint/tree/v6.8.0", 564 | "licenseFile": "node_modules/eslint/LICENSE", 565 | "licenseUrl": "https://github.com/eslint/eslint/blob/v6.8.0/LICENSE", 566 | "licenseTextSource": "file", 567 | "publisher": "Nicholas C. Zakas", 568 | "email": "nicholas+npm@nczconsulting.com" 569 | }, 570 | "husky@3.1.0": { 571 | "name": "husky", 572 | "version": "3.1.0", 573 | "range": "^3.0.5", 574 | "licenses": "MIT", 575 | "repoUrl": "https://github.com/typicode/husky", 576 | "versionedRepoUrl": "https://github.com/typicode/husky/tree/v3.1.0", 577 | "licenseFile": "node_modules/husky/LICENSE", 578 | "licenseUrl": "https://github.com/typicode/husky/blob/v3.1.0/LICENSE", 579 | "licenseTextSource": "file", 580 | "publisher": "Typicode", 581 | "email": "typicode@gmail.com" 582 | }, 583 | "jest@24.9.0": { 584 | "name": "jest", 585 | "version": "24.9.0", 586 | "range": "^24.9.0", 587 | "licenses": "MIT", 588 | "repoUrl": "https://github.com/facebook/jest", 589 | "versionedRepoUrl": "https://github.com/facebook/jest/tree/v24.9.0", 590 | "licenseFile": "node_modules/jest/README.md", 591 | "licenseUrl": "https://github.com/facebook/jest/blob/v24.9.0/README.md", 592 | "licenseTextSource": "spdx" 593 | }, 594 | "npm-run-all@4.1.5": { 595 | "name": "npm-run-all", 596 | "version": "4.1.5", 597 | "range": "^4.1.5", 598 | "licenses": "MIT", 599 | "repoUrl": "https://github.com/mysticatea/npm-run-all", 600 | "versionedRepoUrl": "https://github.com/mysticatea/npm-run-all/tree/v4.1.5", 601 | "licenseFile": "node_modules/npm-run-all/LICENSE", 602 | "licenseUrl": "https://github.com/mysticatea/npm-run-all/blob/v4.1.5/LICENSE", 603 | "licenseTextSource": "file", 604 | "publisher": "Toru Nagashima" 605 | }, 606 | "prettier@1.19.1": { 607 | "name": "prettier", 608 | "version": "1.19.1", 609 | "range": "^1.18.2", 610 | "licenses": "MIT", 611 | "repoUrl": "https://github.com/prettier/prettier", 612 | "versionedRepoUrl": "https://github.com/prettier/prettier/tree/v1.19.1", 613 | "licenseFile": "node_modules/prettier/LICENSE", 614 | "licenseUrl": "https://github.com/prettier/prettier/blob/v1.19.1/LICENSE", 615 | "licenseTextSource": "file", 616 | "publisher": "James Long" 617 | }, 618 | "react-dom@16.6.3": { 619 | "name": "react-dom", 620 | "version": "16.6.3", 621 | "range": "16.6.3", 622 | "licenses": "MIT", 623 | "repoUrl": "https://github.com/facebook/react", 624 | "versionedRepoUrl": "https://github.com/facebook/react/tree/v16.6.3", 625 | "licenseFile": "node_modules/react-dom/LICENSE", 626 | "licenseUrl": "https://github.com/facebook/react/blob/v16.6.3/LICENSE", 627 | "licenseTextSource": "file" 628 | }, 629 | "react@16.6.3": { 630 | "name": "react", 631 | "version": "16.6.3", 632 | "range": "16.6.3", 633 | "licenses": "MIT", 634 | "repoUrl": "https://github.com/facebook/react", 635 | "versionedRepoUrl": "https://github.com/facebook/react/tree/v16.6.3", 636 | "licenseFile": "node_modules/react/LICENSE", 637 | "licenseUrl": "https://github.com/facebook/react/blob/v16.6.3/LICENSE", 638 | "licenseTextSource": "file" 639 | }, 640 | "stylelint-config-prettier@5.3.0": { 641 | "name": "stylelint-config-prettier", 642 | "version": "5.3.0", 643 | "range": "^5.2.0", 644 | "licenses": "MIT", 645 | "repoUrl": "https://github.com/shannonmoeller/stylelint-config-prettier", 646 | "versionedRepoUrl": "https://github.com/shannonmoeller/stylelint-config-prettier/tree/v5.3.0", 647 | "licenseFile": "node_modules/stylelint-config-prettier/license", 648 | "licenseUrl": "https://github.com/shannonmoeller/stylelint-config-prettier/blob/v5.3.0/license", 649 | "licenseTextSource": "file", 650 | "publisher": "Shannon Moeller", 651 | "email": "me@shannonmoeller.com" 652 | }, 653 | "stylelint-config-recommended@2.2.0": { 654 | "name": "stylelint-config-recommended", 655 | "version": "2.2.0", 656 | "range": "^2.2.0", 657 | "licenses": "MIT", 658 | "repoUrl": "https://github.com/stylelint/stylelint-config-recommended", 659 | "versionedRepoUrl": "https://github.com/stylelint/stylelint-config-recommended/tree/v2.2.0", 660 | "licenseFile": "node_modules/stylelint-config-recommended/LICENSE", 661 | "licenseUrl": "https://github.com/stylelint/stylelint-config-recommended/blob/v2.2.0/LICENSE", 662 | "licenseTextSource": "file", 663 | "publisher": "stylelint" 664 | }, 665 | "stylelint-scss@3.18.0": { 666 | "name": "stylelint-scss", 667 | "version": "3.18.0", 668 | "range": "^3.18.0", 669 | "licenses": "MIT", 670 | "repoUrl": "https://github.com/kristerkari/stylelint-scss", 671 | "versionedRepoUrl": "https://github.com/kristerkari/stylelint-scss/tree/v3.18.0", 672 | "licenseFile": "node_modules/stylelint-scss/LICENSE", 673 | "licenseUrl": "https://github.com/kristerkari/stylelint-scss/blob/v3.18.0/LICENSE", 674 | "licenseTextSource": "file", 675 | "publisher": "Krister Kari" 676 | }, 677 | "stylelint@13.7.0": { 678 | "name": "stylelint", 679 | "version": "13.7.0", 680 | "range": "^13.7.0", 681 | "licenses": "MIT", 682 | "repoUrl": "https://github.com/stylelint/stylelint", 683 | "versionedRepoUrl": "https://github.com/stylelint/stylelint/tree/v13.7.0", 684 | "licenseFile": "node_modules/stylelint/LICENSE", 685 | "licenseUrl": "https://github.com/stylelint/stylelint/blob/v13.7.0/LICENSE", 686 | "licenseTextSource": "file", 687 | "publisher": "stylelint" 688 | }, 689 | "ts-jest@24.3.0": { 690 | "name": "ts-jest", 691 | "version": "24.3.0", 692 | "range": "^24.1.0", 693 | "licenses": "MIT", 694 | "repoUrl": "https://github.com/kulshekhar/ts-jest", 695 | "versionedRepoUrl": "https://github.com/kulshekhar/ts-jest/tree/v24.3.0", 696 | "licenseFile": "node_modules/ts-jest/LICENSE.md", 697 | "licenseUrl": "https://github.com/kulshekhar/ts-jest/blob/v24.3.0/LICENSE.md", 698 | "licenseTextSource": "file", 699 | "publisher": "Kulshekhar Kabra", 700 | "email": "kulshekhar@users.noreply.github.com", 701 | "url": "https://github.com/kulshekhar" 702 | }, 703 | "tslint@6.1.3": { 704 | "name": "tslint", 705 | "version": "6.1.3", 706 | "range": "^6.1.3", 707 | "licenses": "Apache-2.0", 708 | "repoUrl": "https://github.com/palantir/tslint", 709 | "versionedRepoUrl": "https://github.com/palantir/tslint/tree/v6.1.3", 710 | "licenseFile": "node_modules/tslint/LICENSE", 711 | "licenseUrl": "https://github.com/palantir/tslint/blob/v6.1.3/LICENSE", 712 | "licenseTextSource": "file" 713 | }, 714 | "typescript@3.9.7": { 715 | "name": "typescript", 716 | "version": "3.9.7", 717 | "range": "^3.9.7", 718 | "licenses": "Apache-2.0", 719 | "repoUrl": "https://github.com/Microsoft/TypeScript", 720 | "versionedRepoUrl": "https://github.com/Microsoft/TypeScript/tree/v3.9.7", 721 | "licenseFile": "node_modules/typescript/LICENSE.txt", 722 | "licenseUrl": "https://github.com/Microsoft/TypeScript/blob/v3.9.7/LICENSE.txt", 723 | "licenseTextSource": "file", 724 | "publisher": "Microsoft Corp." 725 | } 726 | } 727 | } 728 | --------------------------------------------------------------------------------