Move all Grafana assets into subdir. Update plugin dependencies

Signed-off-by: coleramos425 <colramos@amd.com>


[ROCm/rocprofiler-compute commit: 1f44b4faa5]
This commit is contained in:
coleramos425
2024-05-09 13:38:23 +00:00
committed by Cole Ramos
parent 5fd6022d9a
commit 68ef26a9cf
69 changed files with 214 additions and 1899 deletions
@@ -1,8 +0,0 @@
/dashboards
/mongodb_connector
/perfmon
/sample
/soc_params
/utils
/wave_parser
/TestArea
-67
View File
@@ -1,67 +0,0 @@
# -----------------------------------------------------------------------
# NOTE:
# Dependencies are not included as part of Omniperf.
# It's the user's responsibility to accept any licensing implications
# before building the project
# -----------------------------------------------------------------------
FROM ubuntu:20.04
WORKDIR /app
USER root
ENV DEBIAN_FRONTEND noninteractive
ENV TZ "US/Chicago"
ADD grafana_plugins/svg_plugin /var/lib/grafana/plugins/custom-svg
ADD grafana_plugins/omniperfData_plugin /var/lib/grafana/plugins/omniperfData_plugin
RUN apt-get update && \
apt-get install -y apt-transport-https software-properties-common adduser libfontconfig1 wget curl && \
wget https://dl.grafana.com/enterprise/release/grafana-enterprise_8.3.4_amd64.deb &&\
dpkg -i grafana-enterprise_8.3.4_amd64.deb &&\
echo "deb https://packages.grafana.com/enterprise/deb stable main" | tee -a /etc/apt/sources.list.d/grafana.list && \
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get install gnupg && \
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc -O server-5.0.asc &&\
apt-key add server-5.0.asc && \
echo "deb [trusted=yes arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org.list && \
wget -q -O - https://packages.grafana.com/gpg.key | apt-key add - && \
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg > /dev/null && \
apt-get update && \
apt-get install -y mongodb-org && \
apt-get install -y tzdata systemd apt-utils npm vim net-tools && \
mkdir -p /nonexistent && \
/usr/sbin/grafana-cli plugins install michaeldmoore-multistat-panel && \
/usr/sbin/grafana-cli plugins install ae3e-plotly-panel && \
/usr/sbin/grafana-cli plugins install natel-plotly-panel && \
/usr/sbin/grafana-cli plugins install grafana-image-renderer && \
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
apt-get install -y yarn nodejs && \
chown root:grafana /etc/grafana && \
cd /var/lib/grafana/plugins/omniperfData_plugin && \
npm install && \
npm run build && \
apt-get autoremove -y && \
apt-get autoclean -y && \
cd /var/lib/grafana/plugins/custom-svg && \
yarn install && \
yarn build && \
yarn autoclean && \
sed -i "s/ bindIp.*/ bindIp: 0.0.0.0/" /etc/mongod.conf && \
mkdir -p /var/lib/grafana && \
touch /var/lib/grafana/grafana.lib && \
chown grafana:grafana /var/lib/grafana/grafana.lib && \
rm /app/grafana-enterprise_8.3.4_amd64.deb /app/server-5.0.asc
# Overwrite grafana ini file
COPY docker/grafana.ini /etc/grafana
# switch Grafana port to 4000
RUN sed -i "s/^;http_port = 3000/http_port = 4000/" /etc/grafana/grafana.ini && \
sed -i "s/^http_port = 3000/http_port = 4000/" /usr/share/grafana/conf/defaults.ini
# starts mongo and grafana-server at startup
COPY docker/docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT [ "/docker-entrypoint.sh" ]
@@ -0,0 +1 @@
/dashboards
@@ -0,0 +1,75 @@
# -----------------------------------------------------------------------
# NOTE:
# Dependencies are not included as part of Omniperf.
# It's the user's responsibility to accept any licensing implications
# before building the project
# -----------------------------------------------------------------------
FROM ubuntu:22.04
WORKDIR /app
USER root
ENV DEBIAN_FRONTEND noninteractive
ENV TZ "US/Chicago"
ENV NVM_DIR /usr/local/nvm
ENV NODE_VERSION 20.12.2
ADD plugins/omniperf_plugin /var/lib/grafana/plugins/omniperf_plugin
# Install Grafana and MongoDB Community Edition
RUN apt-get update && \
apt-get install -y apt-transport-https software-properties-common wget && \
mkdir -p /etc/apt/keyrings/ && \
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/grafana.gpg > /dev/null && \
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | tee -a /etc/apt/sources.list.d/grafana.list && \
apt-get update && \
apt-get install -y grafana && \
apt-get install -y gnupg curl && \
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor && \
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list && \
apt-get update && \
apt-get install -y mongodb-org
RUN mkdir /usr/local/nvm && \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash && \
. $NVM_DIR/nvm.sh && \
nvm install $NODE_VERSION && \
nvm alias default $NODE_VERSION && \
nvm use default
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
RUN npm --version && \
node --version
# Install Grafana plugins
RUN apt-get install -y tzdata systemd apt-utils npm vim net-tools && \
/usr/sbin/grafana-cli plugins install michaeldmoore-multistat-panel && \
/usr/sbin/grafana-cli plugins install ae3e-plotly-panel && \
/usr/sbin/grafana-cli plugins install natel-plotly-panel && \
/usr/sbin/grafana-cli plugins install grafana-image-renderer && \
/usr/sbin/grafana-cli plugins install aceiot-svg-panel && \
chown root:grafana /etc/grafana && \
cd /var/lib/grafana/plugins/omniperf_plugin && \
npm install && \
npm run build && \
apt-get autoremove -y && \
apt-get autoclean -y && \
sed -i "s/ bindIp.*/ bindIp: 0.0.0.0/" /etc/mongod.conf && \
mkdir -p /var/lib/grafana && \
touch /var/lib/grafana/grafana.lib && \
chown grafana:grafana /var/lib/grafana/grafana.lib
# Overwrite grafana ini file
COPY grafana.ini /etc/grafana
# Switch Grafana port to 4000
RUN sed -i "s/^;http_port = 3000/http_port = 4000/" /etc/grafana/grafana.ini && \
sed -i "s/^http_port = 3000/http_port = 4000/" /usr/share/grafana/conf/defaults.ini
# Starts mongo and grafana-server at startup
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT [ "/docker-entrypoint.sh" ]
@@ -13,6 +13,10 @@ services:
container_name: omniperf-grafana-v1.0
restart: always
build: .
environment:
- GF_PATHS_CONFIG="grafana/etc/grafana.ini"
- GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=amd-omniperf-data-plugin
- GF_DEFAULT_APP_MODE=development
ports:
- "14000:4000"
volumes:
@@ -24,7 +24,7 @@
# SOFTWARE.
##############################################################################el
pushd /var/lib/grafana/plugins/omniperfData_plugin
pushd /var/lib/grafana/plugins/omniperf_plugin
npm run server &
popd
@@ -4,7 +4,7 @@
# change
# possible values : production, development
;app_mode = production
app_mode = development
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
;instance_name = ${HOSTNAME}
@@ -4,4 +4,4 @@ jspm_packages/
# Nuxt.js build / generate output
.nuxt
dist
dist
@@ -0,0 +1,88 @@
module.exports = function(grunt) {
require('load-grunt-tasks')(grunt);
grunt.loadNpmTasks('grunt-execute');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.initConfig({
clean: ["dist"],
copy: {
src_to_dist: {
cwd: 'src',
expand: true,
src: ['**/*', '!**/*.js', '!**/*.scss'],
dest: 'dist'
},
server_to_dist: {
cwd: 'server',
expand: true,
src: ['**/*'],
dest: 'dist/server'
},
pluginDef: {
expand: true,
src: ['README.md'],
dest: 'dist'
}
},
watch: {
rebuild_all: {
files: ['src/**/*'],
tasks: ['default'],
options: {spawn: false}
}
},
babel: {
options: {
sourceMap: true,
presets: ['@babel/preset-env']
},
dist: {
// options: {
// plugins: ['transform-es2015-modules-systemjs', 'transform-es2015-for-of']
// },
files: [{
cwd: 'src',
expand: true,
src: ['**/*.js'],
dest: 'dist',
ext:'.js'
}]
},
distTestNoSystemJs: {
files: [{
cwd: 'src',
expand: true,
src: ['**/*.js'],
dest: 'dist/test',
ext:'.js'
}]
},
distTestsSpecsNoSystemJs: {
files: [{
expand: true,
cwd: 'spec',
src: ['**/*.js'],
dest: 'dist/test/spec',
ext:'.js'
}]
}
},
mochaTest: {
test: {
options: {
reporter: 'spec'
},
src: ['dist/test/spec/test-main.js', 'dist/test/spec/*_spec.js']
}
}
});
grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:server_to_dist', 'copy:pluginDef', 'babel', 'mochaTest']);
};
@@ -0,0 +1,43 @@
{
"name": "amd-omniperf-data-plugin",
"version": "1.0.0",
"description": "",
"main": "server/mongo-proxy.js",
"scripts": {
"build": "./node_modules/grunt-cli/bin/grunt",
"test": "./node_modules/grunt-cli/bin/grunt mochaTest'",
"server": "cd server && node mongo-proxy.js"
},
"author": "Audacious Software Group",
"license": "MIT",
"devDependencies": {
"@babel/preset-env": "^7.24.5",
"babel": "^6.23.0",
"chai": "^4.3.6",
"grunt": "^1.6.1",
"grunt-babel": "^8.0.0",
"grunt-cli": "^1.4.3",
"grunt-contrib-clean": "^1.1.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-uglify": "^5.2.1",
"grunt-contrib-watch": "^1.1.0",
"grunt-mocha-test": "^0.13.3",
"jsdom": "^20.0.0",
"liftoff": "^5.0.0",
"load-grunt-tasks": "^3.5.2",
"prunk": "^1.3.0",
"q": "^1.5.0"
},
"dependencies": {
"body-parser": "^1.18.3",
"config": "^1.30.0",
"express": "^4.17.3",
"fs": "0.0.1-security",
"lodash": "^4.17.21",
"mocha": "^10.4.0",
"moment": "^2.29.4",
"mongodb": "^4.12.1",
"statman-stopwatch": "^2.7.0"
},
"_comments": "Dependencies are not included as part of Omniperf. It's the user's responsibility to accept any licensing implications before building the project."
}
@@ -1,22 +0,0 @@
const { src, dest, series } = require('gulp');
const babel = require('gulp-babel');
function script() {
return src('src/**/*.js')
.pipe(
babel({
presets: ['es2015'],
plugins: [
'transform-es2015-modules-systemjs',
'transform-es2015-for-of',
],
})
)
.pipe(dest('dist'));
}
function srcToDist() {
return src(['src/**/*', '!src/**/*.js']).pipe(dest('dist'));
}
exports.default = series(script, srcToDist);
@@ -1,37 +0,0 @@
{
"name": "amd-omniperf-data-plugin",
"version": "1.0.0",
"description": "",
"main": "server/mongo-proxy.js",
"scripts": {
"build": "gulp",
"test": "echo 'No test found'",
"server": "cd server && node mongo-proxy.js"
},
"author": "Audacious Software Group",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-plugin-transform-es2015-for-of": "^6.23.0",
"babel-plugin-transform-es2015-modules-systemjs": "^6.24.1",
"babel-preset-es2015": "^6.24.1",
"chai": "~4.2.0",
"gulp": "^4.0.2",
"gulp-babel": "^7.0.1",
"jsdom": ">=16.5.0",
"prunk": "^1.3.1",
"q": "^1.5.1"
},
"dependencies": {
"body-parser": "^1.19.0",
"config": "^3.3.1",
"express": "^4.17.1",
"fs": "0.0.1-security",
"lodash": "^4.17.15",
"mocha": "^7.1.2",
"moment": "^2.24.0",
"mongodb": "^3.5.7",
"statman-stopwatch": "^2.18.0"
},
"_comments": "Dependencies are not included as part of Omniperf. It's the user's responsibility to accept any licensing implications before building the project."
}
@@ -1,13 +0,0 @@
/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*
* In order to extend the configuration follow the steps in
* https://grafana.github.io/plugin-tools/docs/advanced-configuration#extending-the-eslint-config
*/
{
"extends": ["@grafana/eslint-config"],
"root": true,
"rules": {
"react/prop-types": "off"
}
}
@@ -1,16 +0,0 @@
/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*
* In order to extend the configuration follow the steps in .config/README.md
*/
module.exports = {
"endOfLine": "auto",
"printWidth": 120,
"trailingComma": "es5",
"semi": true,
"jsxSingleQuote": false,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2
};
@@ -1,16 +0,0 @@
ARG grafana_version=latest
ARG grafana_image=grafana-enterprise
FROM grafana/${grafana_image}:${grafana_version}
# Make it as simple as possible to access the grafana instance for development purposes
# Do NOT enable these settings in a public facing / production grafana instance
ENV GF_AUTH_ANONYMOUS_ORG_ROLE "Admin"
ENV GF_AUTH_ANONYMOUS_ENABLED "true"
ENV GF_AUTH_BASIC_ENABLED "false"
# Set development mode so plugins can be loaded without the need to sign
ENV GF_DEFAULT_APP_MODE "development"
# Inject livereload script into grafana index.html
USER root
RUN sed -i 's/<\/body><\/html>/<script src=\"http:\/\/localhost:35729\/livereload.js\"><\/script><\/body><\/html>/g' /usr/share/grafana/public/views/index.html
@@ -1,164 +0,0 @@
# Default build configuration by Grafana
**This is an auto-generated directory and is not intended to be changed! ⚠️**
The `.config/` directory holds basic configuration for the different tools
that are used to develop, test and build the project. In order to make it updates easier we ask you to
not edit files in this folder to extend configuration.
## How to extend the basic configs?
Bear in mind that you are doing it at your own risk, and that extending any of the basic configuration can lead
to issues around working with the project.
### Extending the ESLint config
Edit the `.eslintrc` file in the project root in order to extend the ESLint configuration.
**Example:**
```json
{
"extends": "./.config/.eslintrc",
"rules": {
"react/prop-types": "off"
}
}
```
---
### Extending the Prettier config
Edit the `.prettierrc.js` file in the project root in order to extend the Prettier configuration.
**Example:**
```javascript
module.exports = {
// Prettier configuration provided by Grafana scaffolding
...require('./.config/.prettierrc.js'),
semi: false,
};
```
---
### Extending the Jest config
There are two configuration in the project root that belong to Jest: `jest-setup.js` and `jest.config.js`.
**`jest-setup.js`:** A file that is run before each test file in the suite is executed. We are using it to
set up the Jest DOM for the testing library and to apply some polyfills. ([link to Jest docs](https://jestjs.io/docs/configuration#setupfilesafterenv-array))
**`jest.config.js`:** The main Jest configuration file that extends the Grafana recommended setup. ([link to Jest docs](https://jestjs.io/docs/configuration))
#### ESM errors with Jest
A common issue found with the current jest config involves importing an npm package which only offers an ESM build. These packages cause jest to error with `SyntaxError: Cannot use import statement outside a module`. To work around this we provide a list of known packages to pass to the `[transformIgnorePatterns](https://jestjs.io/docs/configuration#transformignorepatterns-arraystring)` jest configuration property. If need be this can be extended in the following way:
```javascript
process.env.TZ = 'UTC';
const { grafanaESModules, nodeModulesToTransform } = require('./config/jest/utils');
module.exports = {
// Jest configuration provided by Grafana
...require('./.config/jest.config'),
// Inform jest to only transform specific node_module packages.
transformIgnorePatterns: [nodeModulesToTransform([...grafanaESModules, 'packageName'])],
};
```
---
### Extending the TypeScript config
Edit the `tsconfig.json` file in the project root in order to extend the TypeScript configuration.
**Example:**
```json
{
"extends": "./.config/tsconfig.json",
"compilerOptions": {
"preserveConstEnums": true
}
}
```
---
### Extending the Webpack config
Follow these steps to extend the basic Webpack configuration that lives under `.config/`:
#### 1. Create a new Webpack configuration file
Create a new config file that is going to extend the basic one provided by Grafana.
It can live in the project root, e.g. `webpack.config.ts`.
#### 2. Merge the basic config provided by Grafana and your custom setup
We are going to use [`webpack-merge`](https://github.com/survivejs/webpack-merge) for this.
```typescript
// webpack.config.ts
import type { Configuration } from 'webpack';
import { merge } from 'webpack-merge';
import grafanaConfig from './.config/webpack/webpack.config';
const config = async (env): Promise<Configuration> => {
const baseConfig = await grafanaConfig(env);
return merge(baseConfig, {
// Add custom config here...
output: {
asyncChunks: true,
},
});
};
export default config;
```
#### 3. Update the `package.json` to use the new Webpack config
We need to update the `scripts` in the `package.json` to use the extended Webpack configuration.
**Update for `build`:**
```diff
-"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
+"build": "webpack -c ./webpack.config.ts --env production",
```
**Update for `dev`:**
```diff
-"dev": "webpack -w -c ./.config/webpack/webpack.config.ts --env development",
+"dev": "webpack -w -c ./webpack.config.ts --env development",
```
### Configure grafana image to use when running docker
By default `grafana-enterprise` will be used as the docker image for all docker related commands. If you want to override this behaviour simply alter the `docker-compose.yaml` by adding the following build arg `grafana_image`.
**Example:**
```yaml
version: '3.7'
services:
grafana:
container_name: 'myorg-basic-app'
build:
context: ./.config
args:
grafana_version: ${GRAFANA_VERSION:-9.1.2}
grafana_image: ${GRAFANA_IMAGE:-grafana}
```
In this example we are assigning the environment variable `GRAFANA_IMAGE` to the build arg `grafana_image` with a default value of `grafana`. This will give you the possibility to set the value while running the docker-compose commands which might be convinent in some scenarios.
---
@@ -1,25 +0,0 @@
/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*
* In order to extend the configuration follow the steps in
* https://grafana.github.io/plugin-tools/docs/advanced-configuration#extending-the-jest-config
*/
import '@testing-library/jest-dom';
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
Object.defineProperty(global, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
HTMLCanvasElement.prototype.getContext = () => {};
@@ -1,43 +0,0 @@
/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*
* In order to extend the configuration follow the steps in
* https://grafana.github.io/plugin-tools/docs/advanced-configuration#extending-the-jest-config
*/
const path = require('path');
const { grafanaESModules, nodeModulesToTransform } = require('./jest/utils');
module.exports = {
moduleNameMapper: {
'\\.(css|scss|sass)$': 'identity-obj-proxy',
'react-inlinesvg': path.resolve(__dirname, 'jest', 'mocks', 'react-inlinesvg.tsx'),
},
modulePaths: ['<rootDir>/src'],
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
testEnvironment: 'jest-environment-jsdom',
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{spec,test,jest}.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{spec,test,jest}.{js,jsx,ts,tsx}',
],
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
sourceMaps: 'inline',
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
decorators: false,
dynamicImport: true,
},
},
},
],
},
// Jest will throw `Cannot use import statement outside module` if it tries to load an
// ES module without it being transformed first. ./config/README.md#esm-errors-with-jest
transformIgnorePatterns: [nodeModulesToTransform(grafanaESModules)],
};
@@ -1,25 +0,0 @@
// Due to the grafana/ui Icon component making fetch requests to
// `/public/img/icon/<icon_name>.svg` we need to mock react-inlinesvg to prevent
// the failed fetch requests from displaying errors in console.
import React from 'react';
type Callback = (...args: any[]) => void;
export interface StorageItem {
content: string;
queue: Callback[];
status: string;
}
export const cacheStore: { [key: string]: StorageItem } = Object.create(null);
const SVG_FILE_NAME_REGEX = /(.+)\/(.+)\.svg$/;
const InlineSVG = ({ src }: { src: string }) => {
// testId will be the file name without extension (e.g. `public/img/icons/angle-double-down.svg` -> `angle-double-down`)
const testId = src.replace(SVG_FILE_NAME_REGEX, '$2');
return <svg xmlns="http://www.w3.org/2000/svg" data-testid={testId} viewBox="0 0 24 24" />;
};
export default InlineSVG;
@@ -1,31 +0,0 @@
/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*
* In order to extend the configuration follow the steps in .config/README.md
*/
/*
* This utility function is useful in combination with jest `transformIgnorePatterns` config
* to transform specific packages (e.g.ES modules) in a projects node_modules folder.
*/
const nodeModulesToTransform = (moduleNames) => `node_modules\/(?!(${moduleNames.join('|')})\/)`;
// Array of known nested grafana package dependencies that only bundle an ESM version
const grafanaESModules = [
'.pnpm', // Support using pnpm symlinked packages
'@grafana/schema',
'd3',
'd3-color',
'd3-force',
'd3-interpolate',
'd3-scale-chromatic',
'ol',
'react-colorful',
'rxjs',
'uuid',
];
module.exports = {
nodeModulesToTransform,
grafanaESModules,
};
@@ -1,26 +0,0 @@
/*
* THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY.
*
* In order to extend the configuration follow the steps in
* https://grafana.github.io/plugin-tools/docs/advanced-configuration#extending-the-typescript-config
*/
{
"compilerOptions": {
"alwaysStrict": true,
"declaration": false,
"rootDir": "../src",
"baseUrl": "../src",
"typeRoots": ["../node_modules/@types"],
"resolveJsonModule": true
},
"ts-node": {
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
},
"transpileOnly": true
},
"include": ["../src", "./types"],
"extends": "@grafana/tsconfig"
}
@@ -1,37 +0,0 @@
// Image declarations
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
const content: string;
export default content;
}
// Font declarations
declare module '*.woff';
declare module '*.woff2';
declare module '*.eot';
declare module '*.ttf';
declare module '*.otf';
@@ -1,2 +0,0 @@
export const SOURCE_DIR = 'src';
export const DIST_DIR = 'dist';
@@ -1,40 +0,0 @@
import fs from 'fs';
import path from 'path';
import util from 'util';
import { glob } from 'glob';
import { SOURCE_DIR } from './constants';
export function getPackageJson() {
return require(path.resolve(process.cwd(), 'package.json'));
}
export function getPluginJson() {
return require(path.resolve(process.cwd(), `${SOURCE_DIR}/plugin.json`));
}
export function hasReadme() {
return fs.existsSync(path.resolve(process.cwd(), SOURCE_DIR, 'README.md'));
}
// Support bundling nested plugins by finding all plugin.json files in src directory
// then checking for a sibling module.[jt]sx? file.
export async function getEntries(): Promise<Record<string, string>> {
const pluginsJson = await glob('**/src/**/plugin.json', { absolute: true });
const plugins = await Promise.all(pluginsJson.map((pluginJson) => {
const folder = path.dirname(pluginJson);
return glob(`${folder}/module.{ts,tsx,js,jsx}`, { absolute: true });
})
);
return plugins.reduce((result, modules) => {
return modules.reduce((result, module) => {
const pluginPath = path.dirname(module);
const pluginName = path.relative(process.cwd(), pluginPath).replace(/src\/?/i, '');
const entryName = pluginName === '' ? 'module' : `${pluginName}/module`;
result[entryName] = module;
return result;
}, result);
}, {});
}
@@ -1,201 +0,0 @@
/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*
* In order to extend the configuration follow the steps in
* https://grafana.github.io/plugin-tools/docs/advanced-configuration#extending-the-webpack-config
*/
import CopyWebpackPlugin from 'copy-webpack-plugin';
import ESLintPlugin from 'eslint-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import LiveReloadPlugin from 'webpack-livereload-plugin';
import path from 'path';
import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
import { Configuration } from 'webpack';
import { getPackageJson, getPluginJson, hasReadme, getEntries } from './utils';
import { SOURCE_DIR, DIST_DIR } from './constants';
const pluginJson = getPluginJson();
const config = async (env): Promise<Configuration> => ({
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
context: path.join(process.cwd(), SOURCE_DIR),
devtool: env.production ? 'source-map' : 'eval-source-map',
entry: await getEntries(),
externals: [
'lodash',
'jquery',
'moment',
'slate',
'emotion',
'@emotion/react',
'@emotion/css',
'prismjs',
'slate-plain-serializer',
'@grafana/slate-react',
'react',
'react-dom',
'react-redux',
'redux',
'rxjs',
'react-router',
'react-router-dom',
'd3',
'angular',
'@grafana/ui',
'@grafana/runtime',
'@grafana/data',
// Mark legacy SDK imports as external if their name starts with the "grafana/" prefix
({ request }, callback) => {
const prefix = 'grafana/';
const hasPrefix = (request) => request.indexOf(prefix) === 0;
const stripPrefix = (request) => request.substr(prefix.length);
if (hasPrefix(request)) {
return callback(undefined, stripPrefix(request));
}
callback();
},
],
mode: env.production ? 'production' : 'development',
module: {
rules: [
{
exclude: /(node_modules)/,
test: /\.[tj]sx?$/,
use: {
loader: 'swc-loader',
options: {
jsc: {
baseUrl: './src',
target: 'es2015',
loose: false,
parser: {
syntax: 'typescript',
tsx: true,
decorators: false,
dynamicImport: true,
},
},
},
},
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.s[ac]ss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset/resource',
generator: {
// Keep publicPath relative for host.com/grafana/ deployments
publicPath: `public/plugins/${pluginJson.id}/img/`,
outputPath: 'img/',
filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
},
},
{
test: /\.(woff|woff2|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset/resource',
generator: {
// Keep publicPath relative for host.com/grafana/ deployments
publicPath: `public/plugins/${pluginJson.id}/fonts/`,
outputPath: 'fonts/',
filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
},
},
],
},
output: {
clean: {
keep: new RegExp(`.*?_(amd64|arm(64)?)(.exe)?`),
},
filename: '[name].js',
library: {
type: 'amd',
},
path: path.resolve(process.cwd(), DIST_DIR),
publicPath: '/',
},
plugins: [
new CopyWebpackPlugin({
patterns: [
// If src/README.md exists use it; otherwise the root README
// To `compiler.options.output`
{ from: hasReadme() ? 'README.md' : '../README.md', to: '.', force: true },
{ from: 'plugin.json', to: '.' },
{ from: '../LICENSE', to: '.' },
{ from: '../CHANGELOG.md', to: '.', force: true },
{ from: '**/*.json', to: '.' }, // TODO<Add an error for checking the basic structure of the repo>
{ from: '**/*.svg', to: '.', noErrorOnMissing: true }, // Optional
{ from: '**/*.png', to: '.', noErrorOnMissing: true }, // Optional
{ from: '**/*.html', to: '.', noErrorOnMissing: true }, // Optional
{ from: 'img/**/*', to: '.', noErrorOnMissing: true }, // Optional
{ from: 'libs/**/*', to: '.', noErrorOnMissing: true }, // Optional
{ from: 'static/**/*', to: '.', noErrorOnMissing: true }, // Optional
],
}),
// Replace certain template-variables in the README and plugin.json
new ReplaceInFileWebpackPlugin([
{
dir: DIST_DIR,
files: ['plugin.json', 'README.md'],
rules: [
{
search: /\%VERSION\%/g,
replace: getPackageJson().version,
},
{
search: /\%TODAY\%/g,
replace: new Date().toISOString().substring(0, 10),
},
{
search: /\%PLUGIN_ID\%/g,
replace: pluginJson.id,
},
],
},
]),
new ForkTsCheckerWebpackPlugin({
async: Boolean(env.development),
issue: {
include: [{ file: '**/*.{ts,tsx}' }],
},
typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
}),
new ESLintPlugin({
extensions: ['.ts', '.tsx'],
lintDirtyModulesOnly: Boolean(env.development), // don't lint on start, only lint changed files
}),
...(env.development ? [new LiveReloadPlugin()] : []),
],
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
// handle resolving "rootDir" paths
modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],
unsafeCache: true,
},
});
export default config;
@@ -1,3 +0,0 @@
{
"extends": "./.config/.eslintrc"
}
@@ -1,30 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
node_modules/
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Compiled binary addons (https://nodejs.org/api/addons.html)
dist/
artifacts/
work/
ci/
e2e-results/
# Editors
.idea
@@ -1 +0,0 @@
16
@@ -1,4 +0,0 @@
module.exports = {
// Prettier configuration provided by Grafana scaffolding
...require("./.config/.prettierrc.js")
};
@@ -1,5 +0,0 @@
# Changelog
## 1.0.0 (Unreleased)
Initial release.
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 ACE IoT Solutions LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,44 +0,0 @@
# Grafana Panel Plugin Template
[![Build](https://github.com/grafana/grafana-starter-panel/workflows/CI/badge.svg)](https://github.com/grafana/grafana-starter-panel/actions?query=workflow%3A%22CI%22)
This template is a starting point for building Grafana Panel Plugins in Grafana 7.0+
## What is Grafana Panel Plugin?
Panels are the building blocks of Grafana. They allow you to visualize data in different ways. While Grafana has several types of panels already built-in, you can also build your own panel, to add support for other visualizations.
For more information about panels, refer to the documentation on [Panels](https://grafana.com/docs/grafana/latest/features/panels/panels/)
## Getting started
1. Install dependencies
```bash
yarn install
```
2. Build plugin in development mode or run in watch mode
```bash
yarn dev
```
or
```bash
yarn watch
```
3. Build plugin in production mode
```bash
yarn build
```
## Learn more
- [Build a panel plugin tutorial](https://grafana.com/tutorials/build-a-panel-plugin)
- [Grafana documentation](https://grafana.com/docs/)
- [Grafana Tutorials](https://grafana.com/tutorials/) - Grafana Tutorials are step-by-step guides that help you make the most of Grafana
- [Grafana UI Library](https://developers.grafana.com/ui) - UI components to help you build interfaces using Grafana Design System
@@ -1,15 +0,0 @@
version: '3.0'
services:
grafana:
container_name: 'amd-custom-svg'
build:
context: ./.config
args:
grafana_image: ${GRAFANA_IMAGE:-grafana-enterprise}
grafana_version: ${GRAFANA_VERSION:-9.5.3}
ports:
- 3000:3000/tcp
volumes:
- ./dist:/var/lib/grafana/plugins/amd-custom-svg
- ./provisioning:/etc/grafana/provisioning
@@ -1,2 +0,0 @@
// Jest setup provided by Grafana scaffolding
import './.config/jest-setup';
@@ -1,8 +0,0 @@
// force timezone to UTC to allow tests to work regardless of local timezone
// generally used by snapshots, but can affect specific tests
process.env.TZ = 'UTC';
module.exports = {
// Jest configuration provided by Grafana scaffolding
...require('./.config/jest.config'),
};
@@ -1,74 +0,0 @@
{
"name": "amd-custom-svg",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
"dev": "webpack -w -c ./.config/webpack/webpack.config.ts --env development",
"e2e": "yarn exec cypress install && yarn exec grafana-e2e run",
"e2e:update": "yarn exec cypress install && yarn exec grafana-e2e run --update-screenshots",
"lint": "eslint --cache --ignore-path ./.gitignore --ext .js,.jsx,.ts,.tsx .",
"lint:fix": "yarn run lint --fix",
"server": "docker-compose up --build",
"sign": "npx --yes @grafana/sign-plugin@latest",
"start": "yarn watch",
"test": "jest --watch --onlyChanged",
"test:ci": "jest --passWithNoTests --maxWorkers 4",
"typecheck": "tsc --noEmit"
},
"author": "Audacious Software Group",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.21.4",
"@grafana/e2e": "9.5.3",
"@grafana/e2e-selectors": "9.5.3",
"@grafana/eslint-config": "^6.0.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@swc/core": "1.3.75",
"@swc/helpers": "^0.5.0",
"@swc/jest": "^0.2.26",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.4",
"@types/jest": "^29.5.0",
"@types/lodash": "^4.14.194",
"@types/node": "^18.15.11",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"emotion": "10.0.27",
"eslint-webpack-plugin": "^4.0.1",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"glob": "^10.2.7",
"identity-obj-proxy": "3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"prettier": "^2.8.7",
"react-monaco-editor": "^0.44.0",
"replace-in-file-webpack-plugin": "^1.0.6",
"sass": "1.63.2",
"sass-loader": "13.3.1",
"style-loader": "3.3.3",
"swc-loader": "^0.2.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"tslib": "^2.3.1",
"typescript": "4.8.4",
"webpack": "^5.86.0",
"webpack-cli": "^5.1.4",
"webpack-livereload-plugin": "^3.0.2"
},
"engines": {
"node": ">=14"
},
"dependencies": {
"@emotion/css": "^11.1.3",
"@grafana/data": "9.1.2",
"@grafana/runtime": "9.1.2",
"@grafana/ui": "9.1.2",
"@svgdotjs/svg.js": "^3.1.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"tslib": "2.5.3"
},
"_comments": "Dependencies are not included as part of Omniperf. It's the user's responsibility to accept any licensing implications before building the project.",
"packageManager": "yarn@1.22.19"
}
@@ -1,309 +0,0 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import React, { PureComponent } from 'react';
import { PanelProps } from '@grafana/data';
import { stylesFactory } from '@grafana/ui';
import { SVG, Element as SVGElement, Dom as SVGDom, extend as SVGExtend, Runner as SVGRunner } from '@svgdotjs/svg.js';
import { SVGOptions, SVGIDMapping } from 'types';
import { css } from 'emotion';
interface MappedElements {
[key: string]: SVGElement | SVGDom;
}
interface Props extends PanelProps<SVGOptions> {}
interface PanelState {
addAllIDs: boolean;
svgNode: SVGElement | SVGDom | null;
svgSource: string | null;
mappedElements: MappedElements | null;
svgMappings: SVGIDMapping[];
initFunctionSource: string;
initFunction: Function | null;
eventFunctionSource: string;
eventFunction: Function | null;
initialized: boolean;
}
interface TextMappedElement extends SVGElement {
textElement: Element;
}
SVGExtend(SVGElement, {
openOnClick: function (this: SVGElement, url: string) {
return window.open(url);
},
animateContRotate: function (this: SVGElement, speed: number) {
return (
this.animate(speed)
//@ts-ignore
.ease('-')
//@ts-ignore
.rotate(360)
.loop()
);
},
showOn: function (this: SVGElement, on: boolean) {
if (on) {
this.show();
} else {
this.hide();
}
},
animateOn: function (this: SVGElement, speed: number, on: boolean, animation: Function) {
if (on) {
//@ts-ignore
if (this.timeline()._runners.length === 0) {
animation(this.animate(speed));
} else {
this.timeline().play();
}
} else {
this.timeline().stop();
}
},
stopAnimation: function (this: SVGRunner) {
this.timeline().stop();
},
getParentNode: function (this: SVGElement) {
return this.node.parentNode;
},
getTopNode: function (this: SVGElement) {
let currentNode: Element = this.node as Element;
while (true) {
if (currentNode.parentNode && !currentNode.className.includes('svg-object')) {
currentNode = currentNode.parentNode as Element;
} else {
return currentNode;
}
}
},
});
SVGExtend(SVGDom, {
updateXHTMLFontText: function (this: SVGDom, newText: string) {
let currentElement: Element | TextMappedElement = this.node;
let i = 0;
while (currentElement.localName !== 'xhtml:font') {
if (currentElement.firstElementChild && i < 10) {
currentElement = currentElement.firstElementChild;
i++;
} else {
return;
}
}
currentElement.innerHTML = newText;
},
});
export class SVGPanel extends PureComponent<Props, PanelState> {
constructor(props: any) {
super(props);
this.state = {
addAllIDs: false,
svgNode: null,
svgSource: null,
svgMappings: [],
mappedElements: null,
initFunctionSource: '',
initFunction: null,
eventFunctionSource: '',
eventFunction: null,
initialized: false,
};
}
initializeMappings(svgNode: SVGElement | SVGDom) {
const svgMappings = this.props.options.svgMappings;
let currentElements: MappedElements = { ...this.state.mappedElements };
currentElements = {};
console.log('Current length is ', svgMappings.length);
for (let i = 0; i < svgMappings.length; i++) {
if (svgMappings[i].mappedName !== '') {
currentElements[this.props.options.svgMappings[i].mappedName] = svgNode.findOne(
`#${this.props.options.svgMappings[i].svgId}`
)!;
}
}
this.setState({ mappedElements: currentElements });
}
mapAllIDs(svgNode: SVGDom) {
let svgMappings: SVGIDMapping[] = [...this.props.options.svgMappings];
let nodeFilterID: NodeFilter = {
acceptNode: (node: Element) => {
if (node.id) {
if (node.id !== '') {
return NodeFilter.FILTER_ACCEPT;
}
}
return NodeFilter.FILTER_REJECT;
},
};
let svgWalker = document.createTreeWalker(svgNode.node, NodeFilter.SHOW_ALL, nodeFilterID);
let currentNode: Element | null = svgWalker.currentNode as Element;
while (currentNode) {
if (currentNode && currentNode.id) {
if (svgMappings.filter((mapping) => (currentNode ? mapping.svgId === currentNode.id : false)).length === 0) {
svgMappings.push({ svgId: currentNode.id, mappedName: '' });
}
}
currentNode = svgWalker.nextNode() as Element;
}
this.setState({ svgMappings: [...svgMappings], initialized: false });
this.props.options.svgMappings = [...svgMappings];
this.props.onOptionsChange(this.props.options);
this.forceUpdate();
}
mappingClickHandler(event: React.MouseEvent<HTMLElement, MouseEvent>) {
if (event.target) {
let clicked = event.target as Element;
let loopCount = 0;
let svgMappings: SVGIDMapping[] = [...this.props.options.svgMappings];
if (clicked.id) {
while (clicked.id === '') {
loopCount++;
if (loopCount > 20) {
return;
}
clicked = clicked.parentNode as Element;
}
for (let i = 0; i < svgMappings.length; i++) {
if (svgMappings[i].svgId === clicked.id) {
return;
}
}
svgMappings.push({ svgId: clicked.id, mappedName: '' });
this.setState({ svgMappings: [...svgMappings], initialized: false });
this.props.options.svgMappings = [...svgMappings];
this.props.onOptionsChange(this.props.options);
this.forceUpdate();
}
}
}
renderSVG(element: SVGSVGElement | SVGDom | null) {
if (element) {
if (
this.props.options.initSource !== this.state.initFunctionSource ||
this.state.addAllIDs !== this.props.options.addAllIDs
) {
this.setState({
initFunctionSource: this.props.options.initSource,
addAllIDs: this.props.options.addAllIDs,
initialized: false,
});
}
if (!this.state.initialized) {
console.log('initializing');
let svgNode = SVG(element);
svgNode.clear();
svgNode.svg(this.props.options.svgSource);
svgNode.size(this.props.width, this.props.height);
if (this.props.options.addAllIDs) {
this.mapAllIDs(svgNode);
}
this.initializeMappings(svgNode);
this.setState({ svgNode: svgNode });
try {
this.setState({
initFunction: Function(
'data',
'options',
'svgnode',
'svgmap',
this.props.replaceVariables(this.props.options.initSource)
),
});
if (this.state.mappedElements && this.state.initFunction) {
this.state.initFunction(this.props.data, this.props.options, this.state.svgNode, this.state.mappedElements);
this.setState({ initialized: true });
}
} catch (e) {
this.setState({ initialized: true });
console.log(`User init code failed: ${e}`);
}
}
try {
let eventFunction = this.state.eventFunction;
if (this.props.options.eventSource !== this.state.eventFunctionSource) {
let eventFunctionSource = this.props.options.eventSource;
eventFunction = Function(
'data',
'options',
'svgnode',
'svgmap',
this.props.replaceVariables(eventFunctionSource)
);
this.setState({ eventFunctionSource: eventFunctionSource, eventFunction: eventFunction, initialized: false });
}
if (this.state.mappedElements && eventFunction) {
eventFunction(this.props.data, this.props.options, this.state.svgNode, this.state.mappedElements);
}
} catch (e) {
console.log(`User event code failed: ${e}`);
}
return this.state.svgNode ? this.state.svgNode.svg() : null;
} else {
return null;
}
}
render() {
const styles = this.getStyles();
return (
<div
className={styles.wrapper}
onClick={this.props.options.captureMappings ? this.mappingClickHandler.bind(this) : undefined}
>
<svg
style={{
width: `${this.props.width}px`,
height: `${this.props.height}px`,
}}
className={'svg-object'}
ref={(ref) => this.renderSVG(ref)}
></svg>
</div>
);
}
getStyles = stylesFactory(() => {
return {
wrapper: css`
position: relative;
`,
svg: css`
position: absolute;
top: 0;
left: 0;
`,
textBox: css`
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
`,
};
});
}
@@ -1,90 +0,0 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import React from 'react';
import { PanelProps } from '@grafana/data';
import { SimpleOptions } from 'types';
import { css, cx } from 'emotion';
import { stylesFactory, useTheme } from '@grafana/ui';
interface Props extends PanelProps<SimpleOptions> {}
export const SimplePanel: React.FC<Props> = ({ options, data, width, height }) => {
const theme = useTheme();
const styles = getStyles();
return (
<div
className={cx(
styles.wrapper,
css`
width: ${width}px;
height: ${height}px;
`
)}
>
<svg
className={styles.svg}
width={width}
height={height}
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox={`-${width / 2} -${height / 2} ${width} ${height}`}
>
<g>
<circle style={{ fill: `${theme.isLight ? theme.palette.greenBase : theme.palette.blue95}` }} r={100} />
</g>
</svg>
<div className={styles.textBox}>
{options.showSeriesCount && (
<div
className={css`
font-size: ${theme.typography.size[options.seriesCountSize]};
`}
>
Number of series: {data.series.length}
</div>
)}
<div>Text option value: {options.text}</div>
</div>
</div>
);
};
const getStyles = stylesFactory(() => {
return {
wrapper: css`
position: relative;
`,
svg: css`
position: absolute;
top: 0;
left: 0;
`,
textBox: css`
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
`,
};
});
@@ -1,61 +0,0 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import { SVGDefaults } from './types';
export const props_defaults: SVGDefaults = {
svgNode: `<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="377px"
viewBox="-0.5 -0.5 377 377"
style="max-width:100%;max-height:377px;"
>
<defs />
<g>
<ellipse
cx="188"
cy="188"
rx="185"
ry="185"
fill="#6666ff"
stroke="#ff6666"
stroke-width="7"
pointer-events="all"
/>
</g>
</svg>;
`,
initSource: '',
eventSource: '',
svgMappings: [
{
mappedName: 'barTwo',
svgId: 'rect4526',
},
{
mappedName: 'barThree',
svgId: 'rect4528',
},
],
};
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 81.9 71.52"><defs><style>.cls-1{fill:#84aff1;}.cls-2{fill:#3865ab;}.cls-3{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="42.95" y1="16.88" x2="81.9" y2="16.88" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M55.46,62.43A2,2,0,0,1,54.07,59l4.72-4.54a2,2,0,0,1,2.2-.39l3.65,1.63,3.68-3.64a2,2,0,1,1,2.81,2.84l-4.64,4.6a2,2,0,0,1-2.22.41L60.6,58.26l-3.76,3.61A2,2,0,0,1,55.46,62.43Z"/><path class="cls-2" d="M37,0H2A2,2,0,0,0,0,2V31.76a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V2A2,2,0,0,0,37,0ZM4,29.76V8.84H35V29.76Z"/><path class="cls-3" d="M79.9,0H45a2,2,0,0,0-2,2V31.76a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V2A2,2,0,0,0,79.9,0ZM47,29.76V8.84h31V29.76Z"/><path class="cls-2" d="M37,37.76H2a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V39.76A2,2,0,0,0,37,37.76ZM4,67.52V46.6H35V67.52Z"/><path class="cls-2" d="M79.9,37.76H45a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V39.76A2,2,0,0,0,79.9,37.76ZM47,67.52V46.6h31V67.52Z"/><rect class="cls-1" x="10.48" y="56.95" width="4" height="5.79"/><rect class="cls-1" x="17.43" y="53.95" width="4" height="8.79"/><rect class="cls-1" x="24.47" y="50.95" width="4" height="11.79"/><path class="cls-1" d="M19.47,25.8a6.93,6.93,0,1,1,6.93-6.92A6.93,6.93,0,0,1,19.47,25.8Zm0-9.85a2.93,2.93,0,1,0,2.93,2.93A2.93,2.93,0,0,0,19.47,16Z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,28 +0,0 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import { PanelPlugin } from '@grafana/data';
import { SVGOptions } from './types';
import { SVGPanel } from './SVGjsPanel';
import { optionsBuilder } from 'options';
export const plugin = new PanelPlugin<SVGOptions>(SVGPanel).useFieldConfig().setPanelOptions(optionsBuilder);
@@ -1,349 +0,0 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
import React from 'react';
import Editor from '@monaco-editor/react';
import { SVGIDMapping, SVGOptions } from './types';
import { props_defaults } from 'examples';
import { HorizontalGroup, Label, Tooltip, Button, Input, VerticalGroup, stylesFactory } from '@grafana/ui';
import { PanelOptionsEditorProps, GrafanaTheme, PanelOptionsEditorBuilder } from '@grafana/data';
import { config } from '@grafana/runtime';
import { css } from 'emotion';
interface MonacoEditorProps {
value: string;
theme: string;
language: string;
onChange: (value?: string | undefined) => void;
}
class MonacoEditor extends React.PureComponent<MonacoEditorProps> {
getEditorValue: any | undefined;
editorInstance: any | undefined;
// onSourceChange = () => {
// console.log(this.getEditorValue);
// this.props.onChange(this.getEditorValue());
// };
handleEditorChange = (getEditorValue: any, editorInstance: any) => {
this.props.onChange(getEditorValue);
console.log('here is the current model value:', getEditorValue);
};
onEditorDidMount = (editorInstance: any, getEditorValue: any) => {
this.editorInstance = editorInstance;
this.getEditorValue = getEditorValue;
};
updateDimensions() {
this.editorInstance.layout();
}
render() {
const source = this.props.value;
if (this.editorInstance) {
this.editorInstance.layout();
}
return (
//<div onBlur={this.onSourceChange}>
<Editor
height={'33vh'}
language={this.props.language}
theme={this.props.theme}
value={source}
onMount={this.onEditorDidMount}
onChange={this.handleEditorChange}
/>
//</div>
);
}
}
interface SVGIDMappingProps {
value: SVGIDMapping;
index?: number;
styles?: any;
onChangeItem?: (a: SVGIDMapping, b: number) => void | undefined;
onAdd?: (a: SVGIDMapping) => void;
onDelete?: (a: number) => void;
}
class SvgMapping extends React.PureComponent<SVGIDMappingProps> {
constructor(props: SVGIDMappingProps) {
super(props);
this.state = { ...props.value };
}
render() {
const { value, index, onChangeItem, onAdd, onDelete } = this.props;
return (
<HorizontalGroup>
<Label>SVG ID</Label>
<Input
type="text"
name="svgId"
defaultValue={value.svgId}
onBlur={(e) => {
const svgId = e.currentTarget.value;
this.setState({ svgId: svgId });
onChangeItem && index && onChangeItem({ ...value, svgId: svgId }, index);
}}
/>
<Label>Mapped Name</Label>
<Input
type="text"
name="mappedName"
defaultValue={value.mappedName}
onBlur={(e) => {
const mappedName = e.currentTarget.value;
this.setState({ mappedName: mappedName });
onChangeItem && index && onChangeItem({ ...value, mappedName: mappedName }, index);
}}
/>
{value.svgId && onDelete && index !== undefined && (
<Tooltip content="Delete this mapping" theme={'info'}>
<Button
variant="destructive"
icon="trash-alt"
size="sm"
onClick={() => {
onDelete(index);
}}
>
Remove
</Button>
</Tooltip>
)}
{!value.svgId && onAdd && (
<Tooltip content="Add a new SVG Element ID to svgmap property mapping manually" theme={'info'}>
<Button
variant="secondary"
size="sm"
icon="plus-circle"
onClick={() => {
onAdd(this.state as SVGIDMapping);
}}
>
Add
</Button>
</Tooltip>
)}
</HorizontalGroup>
);
}
}
class SvgMappings extends React.PureComponent<PanelOptionsEditorProps<SVGIDMapping[]>> {
onChangeItem = (updatedMapping: SVGIDMapping, index: number) => {
let newMappings = [...this.props.value];
newMappings[index] = updatedMapping;
this.props.onChange(newMappings);
};
onAdd = (newMapping: SVGIDMapping) => {
if (newMapping.svgId !== '') {
let newMappings = [...this.props.value, newMapping];
this.props.onChange(newMappings);
}
};
onDelete = (index: number) => {
let newMappings = [...this.props.value];
newMappings.splice(index, 1);
this.props.onChange(newMappings);
};
render() {
const styles = getStyles(config.theme);
const svgMappings = this.props.value;
return (
<VerticalGroup>
<HorizontalGroup>
<Tooltip content="Clear all SVG Element ID to svgmap property mappings" theme="info">
<Button
variant="destructive"
icon="trash-alt"
size="sm"
onClick={() => {
this.props.onChange([]);
}}
>
Clear All
</Button>
</Tooltip>
<SvgMapping value={{ svgId: '', mappedName: '' }} styles={styles} onAdd={this.onAdd} />
</HorizontalGroup>
{svgMappings.map((currentMapping: SVGIDMapping, index: number) => {
return (
<SvgMapping
key={currentMapping.svgId}
value={currentMapping}
index={index}
onChangeItem={this.onChangeItem}
onDelete={this.onDelete}
styles={styles}
/>
);
})}
</VerticalGroup>
);
}
}
// ---------------------------------------
export const optionsBuilder = (builder: PanelOptionsEditorBuilder<SVGOptions>) => {
return builder
.addBooleanSwitch({
category: ['SVG Document'],
path: 'svgAutoComplete',
name: 'Enable SVG AutoComplete',
description: 'Enable editor autocompletion, optional as it can be buggy on large documents',
})
.addCustomEditor({
category: ['SVG Document'],
path: 'svgSource',
name: 'SVG Document',
description: `Editor for SVG Document, while small tweaks can be made here, we recommend using a dedicated
Graphical SVG Editor and simply pasting the resulting XML here`,
id: 'svgSource',
defaultValue: props_defaults.svgNode,
//editor: (props) => {
editor: function myNamedFuntion(props) {
const grafanaTheme = config.theme.name;
return (
<MonacoEditor
language="xml"
theme={grafanaTheme === 'Grafana Light' ? 'vs-light' : 'vs-dark'}
value={props.value}
onChange={props.onChange}
/>
);
},
})
.addBooleanSwitch({
category: ['User JS Render'],
path: 'eventAutoComplete',
name: 'Enable Render JS AutoComplete',
description: 'Enable editor autocompletion, optional as it can be buggy on large documents',
defaultValue: true,
})
.addCustomEditor({
category: ['User JS Render'],
path: 'eventSource',
name: 'User JS Render Code',
description: `The User JS Render code is executed whenever new data is available, the root svg document is available as 'svgnode',
and elements you've mapped using the SVG Mapping tools below are available as properties on the 'svgmap' object.
The Grafana DataFrame is provided as 'data' and the 'options' object can be used to pass values and references between
the Render context and the Init context`,
id: 'eventSource',
defaultValue: props_defaults.eventSource,
//editor: (props) => {
editor: function myNamedFunc(props) {
const grafanaTheme = config.theme.name;
return (
<MonacoEditor
language="javascript"
theme={grafanaTheme === 'Grafana Light' ? 'vs-light' : 'vs-dark'}
value={props.value}
onChange={props.onChange}
/>
);
},
})
.addBooleanSwitch({
category: ['User JS Init'],
path: 'initAutoComplete',
name: 'Enable Init JS AutoComplete',
description: 'Enable editor autocompletion, optional as it can be buggy on large documents',
defaultValue: true,
})
.addCustomEditor({
category: ['User JS Init'],
path: 'initSource',
name: 'User JS Init Code',
description: `The User JS Init code is executed once when the panel loads, you can use this to define helper functions that
you later reference in the User JS Render code section. The sections have identical execution contexts, and any
JS objects you want to reference between them will need to be attached to the options object as properties`,
id: 'initSource',
defaultValue: props_defaults.initSource,
//editor: (props) => {
editor: function myNamedFunc(props) {
const grafanaTheme = config.theme.name;
return (
<MonacoEditor
language="javascript"
theme={grafanaTheme === 'Grafana Light' ? 'vs-light' : 'vs-dark'}
value={props.value}
onChange={props.onChange}
/>
);
},
})
.addBooleanSwitch({
category: ['SVG Mapping'],
path: 'addAllIDs',
name: 'Add all SVG Element IDs',
description:
'Parse the SVG Document for Elements with IDs assigned and automatically add them to the mapping list',
defaultValue: false,
})
.addBooleanSwitch({
category: ['SVG Mapping'],
path: 'captureMappings',
name: 'Enable SVG Mapping on Click',
description:
'When activated, clicking an element in the panel will attempt to map the clicked element or its nearest parent element with an ID assigned',
defaultValue: false,
})
.addCustomEditor({
category: ['SVG Mapping'],
id: 'svgMappings',
path: 'svgMappings',
name: 'SVG Mappings',
description:
'The SVG ID should match an element in the SVG document with an existing ID tag, the element will be attached to the "svgmap" object in the user code execution contexts as a property using the Mapped Name provided below',
defaultValue: props_defaults.svgMappings,
editor: SvgMappings,
});
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
colorPicker: css`
padding: 0 ${theme.spacing.sm};
`,
inputPrefix: css`
display: flex;
align-items: center;
`,
trashIcon: css`
color: ${theme.colors.textWeak};
cursor: pointer;
//
&:hover {
color: ${theme.colors.text};
}
`,
addIcon: css`
color: ${theme.colors.textWeak};
cursor: pointer;
//
&:hover {
color: ${theme.colors.text};
}
`,
};
});
@@ -1,25 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/master/docs/sources/developers/plugins/plugin.schema.json",
"type": "panel",
"name": "custom-svg",
"id": "amd-custom-svg",
"info": {
"description": "",
"author": {
"name": "Cole Ramos",
"url": ""
},
"keywords": [],
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"screenshots": [],
"version": "%VERSION%",
"updated": "%TODAY%"
},
"dependencies": {
"grafanaDependency": ">=7.0.0",
"plugins": []
}
}
@@ -1,50 +0,0 @@
// ################################################################################
// # Copyright (c) 2020 ACE IoT Solutions LLC
// #
// # Permission is hereby granted, free of charge, to any person obtaining a copy
// # of this software and associated documentation files (the "Software"), to deal
// # in the Software without restriction, including without limitation the rights
// # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// # copies of the Software, and to permit persons to whom the Software is
// # furnished to do so, subject to the following conditions:
// # The above copyright notice and this permission notice shall be included in all
// # copies or substantial portions of the Software.
// #
// # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// # SOFTWARE.
// ################################################################################
type SeriesSize = 'sm' | 'md' | 'lg';
export interface SimpleOptions {
text: string;
showSeriesCount: boolean;
seriesCountSize: SeriesSize;
}
export interface SVGIDMapping {
svgId: string;
mappedName: string;
}
export interface SVGOptions {
captureMappings: boolean;
addAllIDs: boolean;
svgSource: string;
svgAutocomplete: boolean;
eventSource: string;
eventAutocomplete: boolean;
initSource: string;
initAutocomplete: boolean;
svgMappings: SVGIDMapping[];
}
export interface SVGDefaults {
svgNode: string; //svg default text
initSource: string; //init default text
eventSource: string; //render default text
svgMappings: SVGIDMapping[]; //default mappings
}
@@ -1,3 +0,0 @@
{
"extends": "./.config/tsconfig.json"
}